Merge "SoundTriggerLogger replaced with utils.EventLogger"
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 9ac3e41..9d363c8 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -80,6 +80,7 @@
import android.os.RemoteCallback;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -619,7 +620,7 @@
return blobInfos;
}
- private void deleteBlobInternal(long blobId, int callingUid) {
+ private void deleteBlobInternal(long blobId) {
synchronized (mBlobsLock) {
mBlobsMap.entrySet().removeIf(entry -> {
final BlobMetadata blobMetadata = entry.getValue();
@@ -1612,10 +1613,7 @@
@Override
@NonNull
public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only system uid is allowed to call "
- + "queryBlobsForUser()");
- }
+ verifyCallerIsSystemUid("queryBlobsForUser");
final int resolvedUserId = userId == USER_CURRENT
? ActivityManager.getCurrentUser() : userId;
@@ -1629,13 +1627,9 @@
@Override
public void deleteBlob(long blobId) {
- final int callingUid = Binder.getCallingUid();
- if (callingUid != Process.SYSTEM_UID) {
- throw new SecurityException("Only system uid is allowed to call "
- + "deleteBlob()");
- }
+ verifyCallerIsSystemUid("deleteBlob");
- deleteBlobInternal(blobId, callingUid);
+ deleteBlobInternal(blobId);
}
@Override
@@ -1716,6 +1710,18 @@
return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this,
in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
}
+
+ /**
+ * Verify if the caller is an admin user's app with system uid
+ */
+ private void verifyCallerIsSystemUid(final String operation) {
+ if (UserHandle.getCallingAppId() != Process.SYSTEM_UID
+ || !mContext.getSystemService(UserManager.class)
+ .isUserAdmin(UserHandle.getCallingUserId())) {
+ throw new SecurityException("Only admin user's app with system uid"
+ + "are allowed to call #" + operation);
+ }
+ }
}
static final class DumpArgs {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index f9fb0d0..92716f4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1094,27 +1094,41 @@
private void updateNetworkBytesLocked() {
mTotalNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+ if (mTotalNetworkDownloadBytes < 0) {
+ // Legacy apps may have provided invalid negative values. Ignore invalid values.
+ mTotalNetworkDownloadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+ }
mTotalNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
+ if (mTotalNetworkUploadBytes < 0) {
+ // Legacy apps may have provided invalid negative values. Ignore invalid values.
+ mTotalNetworkUploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+ }
+ // Minimum network chunk bytes has had data validation since its introduction, so no
+ // need to do validation again.
mMinimumNetworkChunkBytes = job.getMinimumNetworkChunkBytes();
if (pendingWork != null) {
for (int i = 0; i < pendingWork.size(); i++) {
- if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- // If any component of the job has unknown usage, we don't have a
- // complete picture of what data will be used, and we have to treat the
- // entire up/download as unknown.
- long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
- if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
+ if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN && downloadBytes > 0) {
+ // If any component of the job has unknown usage, we won't have a
+ // complete picture of what data will be used. However, we use what we are given
+ // to get us as close to the complete picture as possible.
+ if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
mTotalNetworkDownloadBytes += downloadBytes;
+ } else {
+ mTotalNetworkDownloadBytes = downloadBytes;
}
}
- if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- // If any component of the job has unknown usage, we don't have a
- // complete picture of what data will be used, and we have to treat the
- // entire up/download as unknown.
- long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
- if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+ long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
+ if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN && uploadBytes > 0) {
+ // If any component of the job has unknown usage, we won't have a
+ // complete picture of what data will be used. However, we use what we are given
+ // to get us as close to the complete picture as possible.
+ if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
mTotalNetworkUploadBytes += uploadBytes;
+ } else {
+ mTotalNetworkUploadBytes = uploadBytes;
}
}
final long chunkBytes = pendingWork.get(i).getMinimumNetworkChunkBytes();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 581a545..17b8746 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -701,6 +701,10 @@
return;
}
final long totalDischargeMah = mAnalyst.getBatteryScreenOffDischargeMah();
+ if (totalDischargeMah == 0) {
+ Slog.i(TAG, "Total discharge was 0");
+ return;
+ }
final long batteryCapacityMah = mBatteryManagerInternal.getBatteryFullCharge() / 1000;
final long estimatedLifeHours = batteryCapacityMah * totalScreenOffDurationMs
/ totalDischargeMah / HOUR_IN_MILLIS;
diff --git a/cmds/uiautomator/OWNERS b/cmds/uiautomator/OWNERS
new file mode 100644
index 0000000..5c7f452
--- /dev/null
+++ b/cmds/uiautomator/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 833089
+peykov@google.com
+normancheung@google.com
+guran@google.com
diff --git a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
index 3b14be7..24727c5 100644
--- a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
+++ b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
@@ -107,7 +107,7 @@
DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
int rotation = display.getRotation();
Point size = new Point();
- display.getSize(size);
+ display.getRealSize(size);
AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x,
size.y);
}
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index ab198b3..488292d 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -139,7 +139,7 @@
serializer.attribute("", "id", Integer.toString(displayId));
int rotation = display.getRotation();
Point size = new Point();
- display.getSize(size);
+ display.getRealSize(size);
for (int i = 0, n = windows.size(); i < n; ++i) {
dumpWindowRec(windows.get(i), serializer, i, size.x, size.y, rotation);
}
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index 6fd2bf2..1bcd343e 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -767,7 +767,7 @@
if(root != null) {
Display display = getAutomatorBridge().getDefaultDisplay();
Point size = new Point();
- display.getSize(size);
+ display.getRealSize(size);
AccessibilityNodeInfoDumper.dumpWindowToFile(root,
new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName),
display.getRotation(), size.x, size.y);
diff --git a/core/api/current.txt b/core/api/current.txt
index 98e2f8b..7eee5f4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -18434,7 +18434,7 @@
method public android.graphics.Point getLeftEyePosition();
method public android.graphics.Point getMouthPosition();
method public android.graphics.Point getRightEyePosition();
- method public int getScore();
+ method @IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) public int getScore();
field public static final int ID_UNSUPPORTED = -1; // 0xffffffff
field public static final int SCORE_MAX = 100; // 0x64
field public static final int SCORE_MIN = 1; // 0x1
@@ -18449,7 +18449,7 @@
method @NonNull public android.hardware.camera2.params.Face.Builder setLeftEyePosition(@NonNull android.graphics.Point);
method @NonNull public android.hardware.camera2.params.Face.Builder setMouthPosition(@NonNull android.graphics.Point);
method @NonNull public android.hardware.camera2.params.Face.Builder setRightEyePosition(@NonNull android.graphics.Point);
- method @NonNull public android.hardware.camera2.params.Face.Builder setScore(int);
+ method @NonNull public android.hardware.camera2.params.Face.Builder setScore(@IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) int);
}
public final class InputConfiguration {
@@ -19490,6 +19490,7 @@
public final class GnssCapabilities implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<android.location.GnssSignalType> getGnssSignalTypes();
method public boolean hasAntennaInfo();
method public boolean hasGeofencing();
method @Deprecated public boolean hasGnssAntennaInfo();
@@ -19523,6 +19524,7 @@
ctor public GnssCapabilities.Builder();
ctor public GnssCapabilities.Builder(@NonNull android.location.GnssCapabilities);
method @NonNull public android.location.GnssCapabilities build();
+ method @NonNull public android.location.GnssCapabilities.Builder setGnssSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
method @NonNull public android.location.GnssCapabilities.Builder setHasAntennaInfo(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasGeofencing(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean);
@@ -19735,6 +19737,16 @@
field @Deprecated public static final int STATUS_READY = 1; // 0x1
}
+ public final class GnssSignalType implements android.os.Parcelable {
+ method @NonNull public static android.location.GnssSignalType create(int, @FloatRange(from=0.0f, fromInclusive=false) double, @NonNull String);
+ method public int describeContents();
+ method @FloatRange(from=0.0f, fromInclusive=false) public double getCarrierFrequencyHz();
+ method @NonNull public String getCodeType();
+ method public int getConstellationType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssSignalType> CREATOR;
+ }
+
public final class GnssStatus implements android.os.Parcelable {
method public int describeContents();
method @FloatRange(from=0, to=360) public float getAzimuthDegrees(@IntRange(from=0) int);
@@ -26529,6 +26541,7 @@
method public boolean onKeyMultiple(int, int, @NonNull android.view.KeyEvent);
method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
method public void onMediaViewSizeChanged(@Px int, @Px int);
+ method public void onRecordingStarted(@NonNull String);
method public abstract void onRelease();
method public void onResetInteractiveApp();
method public abstract boolean onSetSurface(@Nullable android.view.Surface);
@@ -26554,6 +26567,7 @@
method @CallSuper public void requestCurrentChannelUri();
method @CallSuper public void requestCurrentTvInputId();
method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method @CallSuper public void requestStartRecording(@Nullable android.net.Uri);
method @CallSuper public void requestStreamVolume();
method @CallSuper public void requestTrackInfoList();
method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle);
@@ -26585,6 +26599,7 @@
method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
method @Nullable public android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
+ method public void notifyRecordingStarted(@NonNull String);
method public void onAttachedToWindow();
method public void onDetachedFromWindow();
method public void onLayout(boolean, int, int, int, int);
@@ -26626,6 +26641,7 @@
method public void onRequestCurrentChannelUri(@NonNull String);
method public void onRequestCurrentTvInputId(@NonNull String);
method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method public void onRequestStartRecording(@NonNull String, @Nullable android.net.Uri);
method public void onRequestStreamVolume(@NonNull String);
method public void onRequestTrackInfoList(@NonNull String);
method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect);
@@ -50100,7 +50116,7 @@
method public void dispatchCreateViewTranslationRequest(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @NonNull android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
- method protected void dispatchDraw(android.graphics.Canvas);
+ method protected void dispatchDraw(@NonNull android.graphics.Canvas);
method public void dispatchDrawableHotspotChanged(float, float);
method @CallSuper public void dispatchFinishTemporaryDetach();
method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
@@ -50138,7 +50154,7 @@
method @NonNull public android.view.WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimation, @NonNull android.view.WindowInsetsAnimation.Bounds);
method @Deprecated public void dispatchWindowSystemUiVisiblityChanged(int);
method public void dispatchWindowVisibilityChanged(int);
- method @CallSuper public void draw(android.graphics.Canvas);
+ method @CallSuper public void draw(@NonNull android.graphics.Canvas);
method @CallSuper public void drawableHotspotChanged(float, float);
method @CallSuper protected void drawableStateChanged();
method public android.view.View findFocus();
@@ -50431,9 +50447,9 @@
method @CallSuper protected void onDetachedFromWindow();
method protected void onDisplayHint(int);
method public boolean onDragEvent(android.view.DragEvent);
- method protected void onDraw(android.graphics.Canvas);
- method public void onDrawForeground(android.graphics.Canvas);
- method protected final void onDrawScrollBars(android.graphics.Canvas);
+ method protected void onDraw(@NonNull android.graphics.Canvas);
+ method public void onDrawForeground(@NonNull android.graphics.Canvas);
+ method protected final void onDrawScrollBars(@NonNull android.graphics.Canvas);
method public boolean onFilterTouchEventForSecurity(android.view.MotionEvent);
method @CallSuper protected void onFinishInflate();
method public void onFinishTemporaryDetach();
@@ -50919,7 +50935,7 @@
ctor public View.DragShadowBuilder(android.view.View);
ctor public View.DragShadowBuilder();
method public final android.view.View getView();
- method public void onDrawShadow(android.graphics.Canvas);
+ method public void onDrawShadow(@NonNull android.graphics.Canvas);
method public void onProvideShadowMetrics(android.graphics.Point, android.graphics.Point);
}
@@ -51149,7 +51165,7 @@
method public void dispatchSetActivated(boolean);
method public void dispatchSetSelected(boolean);
method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
- method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
+ method protected boolean drawChild(@NonNull android.graphics.Canvas, android.view.View, long);
method public void endViewTransition(android.view.View);
method public android.view.View focusSearch(android.view.View, int);
method public void focusableViewAvailable(android.view.View);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 352b4f9..70b89b8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6346,12 +6346,21 @@
public final class AudioPlaybackConfiguration implements android.os.Parcelable {
method public int getClientPid();
method public int getClientUid();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMutedBy();
method public int getPlayerInterfaceId();
method public android.media.PlayerProxy getPlayerProxy();
method public int getPlayerState();
method public int getPlayerType();
method @IntRange(from=0) public int getSessionId();
method public boolean isActive();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_UNKNOWN = -1; // 0xffffffff
+ field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20
field public static final int PLAYER_STATE_IDLE = 1; // 0x1
field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
field public static final int PLAYER_STATE_RELEASED = 0; // 0x0
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 89601bc..45515dd 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -117,27 +117,27 @@
/**
* Bundle key used for the {@link String} account type in session bundle.
* This is used in the default implementation of
- * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+ * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
*/
private static final String KEY_AUTH_TOKEN_TYPE =
"android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
/**
* Bundle key used for the {@link String} array of required features in
* session bundle. This is used in the default implementation of
- * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+ * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
*/
private static final String KEY_REQUIRED_FEATURES =
"android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
/**
* Bundle key used for the {@link Bundle} options in session bundle. This is
* used in default implementation of {@link #startAddAccountSession} and
- * {@link startUpdateCredentialsSession}.
+ * {@link #startUpdateCredentialsSession}.
*/
private static final String KEY_OPTIONS =
"android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
/**
* Bundle key used for the {@link Account} account in session bundle. This is used
- * used in default implementation of {@link startUpdateCredentialsSession}.
+ * used in default implementation of {@link #startUpdateCredentialsSession}.
*/
private static final String KEY_ACCOUNT =
"android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 267e5b6..dc325ff 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1368,9 +1368,31 @@
public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
+ /**
+ * Prevent an app from being placed into app standby buckets.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_APP_STANDBY;
+
+ /**
+ * Prevent an app from being placed into forced app standby.
+ * {@link ActivityManager#isBackgroundRestricted()}
+ * {@link #OP_RUN_ANY_IN_BACKGROUND}
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 124;
+ public static final int _NUM_OP = 126;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1869,6 +1891,28 @@
*/
public static final String OPSTR_RUN_LONG_JOBS = "android:run_long_jobs";
+ /**
+ * Prevent an app from being placed into app standby buckets.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+ "android:system_exempt_from_app_standby";
+
+ /**
+ * Prevent an app from being placed into forced app standby.
+ * {@link ActivityManager#isBackgroundRestricted()}
+ * {@link #OP_RUN_ANY_IN_BACKGROUND}
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+ "android:system_exempt_from_forced_app_standby";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2350,7 +2394,13 @@
new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED,
OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED")
.setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
- .setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
+ .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+ OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+ "SYSTEM_EXEMPT_FROM_APP_STANDBY").build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+ OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+ "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
};
/**
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b0c6cbc..e981581 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -29,6 +29,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
+import android.os.UserHandle;
import android.permission.PermissionManager;
import android.util.ArraySet;
@@ -297,7 +298,7 @@
public boolean checkCallingUid() {
final int callingUid = Binder.getCallingUid();
if (callingUid != Process.ROOT_UID
- && callingUid != Process.SYSTEM_UID
+ && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID
&& callingUid != mAttributionSourceState.uid) {
return false;
}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 645a1ac..80f3264 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -43,17 +43,20 @@
// Attribute strings for reading/writing properties to/from XML.
private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
private static final String ATTR_START_WITH_PARENT = "startWithParent";
+ private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
/** Index values of each property (to indicate whether they are present in this object). */
@IntDef(prefix = "INDEX_", value = {
INDEX_SHOW_IN_LAUNCHER,
INDEX_START_WITH_PARENT,
+ INDEX_SHOW_IN_SETTINGS,
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
}
private static final int INDEX_SHOW_IN_LAUNCHER = 0;
private static final int INDEX_START_WITH_PARENT = 1;
+ private static final int INDEX_SHOW_IN_SETTINGS = 2;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -87,6 +90,37 @@
public static final int SHOW_IN_LAUNCHER_NO = 2;
/**
+ * Possible values for whether or how to show this user in the Settings app.
+ * @hide
+ */
+ @IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+ SHOW_IN_SETTINGS_WITH_PARENT,
+ SHOW_IN_SETTINGS_SEPARATE,
+ SHOW_IN_SETTINGS_NO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShowInSettings {
+ }
+ /**
+ * Suggests that the Settings app should show this user's apps in the main tab.
+ * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+ * this user is a profile, then its apps should be shown alongside its parent's apps.
+ * @hide
+ */
+ public static final int SHOW_IN_SETTINGS_WITH_PARENT = 0;
+ /**
+ * Suggests that the Settings app should show this user's apps, but separately from the apps of
+ * this user's parent.
+ * @hide
+ */
+ public static final int SHOW_IN_SETTINGS_SEPARATE = 1;
+ /**
+ * Suggests that the Settings app should not show this user.
+ * @hide
+ */
+ public static final int SHOW_IN_SETTINGS_NO = 2;
+
+ /**
* Reference to the default user properties for this user's user type.
* <li>If non-null, then any absent property will use the default property from here instead.
* <li>If null, then any absent property indicates that the caller lacks permission to see it,
@@ -130,6 +164,7 @@
}
if (hasManagePermission) {
// Add items that require MANAGE_USERS or stronger.
+ setShowInSettings(orig.getShowInSettings());
}
if (hasQueryOrManagePermission) {
// Add items that require QUERY_USERS or stronger.
@@ -183,6 +218,33 @@
private @ShowInLauncher int mShowInLauncher;
/**
+ * Returns whether, and how, a user should be shown in the Settings app.
+ * This is generally inapplicable for non-profile users.
+ *
+ * Possible return values include
+ * {@link #SHOW_IN_SETTINGS_WITH_PARENT}},
+ * {@link #SHOW_IN_SETTINGS_SEPARATE},
+ * and {@link #SHOW_IN_SETTINGS_NO}.
+ *
+ * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this
+ * property.
+ *
+ * @return whether, and how, a profile should be shown in the Settings.
+ * @hide
+ */
+ public @ShowInSettings int getShowInSettings() {
+ if (isPresent(INDEX_SHOW_IN_SETTINGS)) return mShowInSettings;
+ if (mDefaultProperties != null) return mDefaultProperties.mShowInSettings;
+ throw new SecurityException("You don't have permission to query mShowInSettings");
+ }
+ /** @hide */
+ public void setShowInSettings(@ShowInSettings int val) {
+ this.mShowInSettings = val;
+ setPresent(INDEX_SHOW_IN_SETTINGS);
+ }
+ private @ShowInSettings int mShowInSettings;
+
+ /**
* Returns whether a profile should be started when its parent starts (unless in quiet mode).
* This only applies for users that have parents (i.e. for profiles).
* @hide
@@ -206,6 +268,7 @@
+ "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
+ ", mShowInLauncher=" + getShowInLauncher()
+ ", mStartWithParent=" + getStartWithParent()
+ + ", mShowInSettings=" + getShowInSettings()
+ "}";
}
@@ -219,6 +282,7 @@
pw.println(prefix + " mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
pw.println(prefix + " mShowInLauncher=" + getShowInLauncher());
pw.println(prefix + " mStartWithParent=" + getStartWithParent());
+ pw.println(prefix + " mShowInSettings=" + getShowInSettings());
}
/**
@@ -258,6 +322,8 @@
case ATTR_START_WITH_PARENT:
setStartWithParent(parser.getAttributeBoolean(i));
break;
+ case ATTR_SHOW_IN_SETTINGS:
+ setShowInSettings(parser.getAttributeInt(i));
default:
Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
}
@@ -281,6 +347,9 @@
if (isPresent(INDEX_START_WITH_PARENT)) {
serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
}
+ if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
+ serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
+ }
}
// For use only with an object that has already had any permission-lacking fields stripped out.
@@ -289,6 +358,7 @@
dest.writeLong(mPropertiesPresent);
dest.writeInt(mShowInLauncher);
dest.writeBoolean(mStartWithParent);
+ dest.writeInt(mShowInSettings);
}
/**
@@ -301,6 +371,7 @@
mPropertiesPresent = source.readLong();
mShowInLauncher = source.readInt();
mStartWithParent = source.readBoolean();
+ mShowInSettings = source.readInt();
}
@Override
@@ -327,6 +398,7 @@
// UserProperties fields and their default values.
private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
private boolean mStartWithParent = false;
+ private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
mShowInLauncher = showInLauncher;
@@ -338,21 +410,30 @@
return this;
}
+ /** Sets the value for {@link #mShowInSettings} */
+ public Builder setShowInSettings(@ShowInSettings int showInSettings) {
+ mShowInSettings = showInSettings;
+ return this;
+ }
+
/** Builds a UserProperties object with *all* values populated. */
public UserProperties build() {
return new UserProperties(
mShowInLauncher,
- mStartWithParent);
+ mStartWithParent,
+ mShowInSettings);
}
} // end Builder
/** Creates a UserProperties with the given properties. Intended for building default values. */
private UserProperties(
@ShowInLauncher int showInLauncher,
- boolean startWithParent) {
+ boolean startWithParent,
+ @ShowInSettings int showInSettings) {
mDefaultProperties = null;
setShowInLauncher(showInLauncher);
setStartWithParent(startWithParent);
+ setShowInSettings(showInSettings);
}
}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 30ee118..c9a0626 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -62,10 +62,10 @@
* <p>The execution can potentially launch UI flows to collect user consent to using a
* credential, display a picker when multiple credentials exist, etc.
*
- * @param request the request specifying type(s) of credentials to get from the user.
- * @param cancellationSignal an optional signal that allows for cancelling this call.
- * @param executor the callback will take place on this {@link Executor}.
- * @param callback the callback invoked when the request succeeds or fails.
+ * @param request the request specifying type(s) of credentials to get from the user
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
*/
public void executeGetCredential(
@NonNull GetCredentialRequest request,
@@ -101,10 +101,10 @@
* <p>The execution can potentially launch UI flows to collect user consent to creating
* or storing the new credential, etc.
*
- * @param request the request specifying type(s) of credentials to get from the user.
- * @param cancellationSignal an optional signal that allows for cancelling this call.
- * @param executor the callback will take place on this {@link Executor}.
- * @param callback the callback invoked when the request succeeds or fails.
+ * @param request the request specifying type(s) of credentials to get from the user
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
*/
public void executeCreateCredential(
@NonNull CreateCredentialRequest request,
@@ -135,6 +135,44 @@
}
}
+ /**
+ * Clears the current user credential session from all credential providers.
+ *
+ * <p>Usually invoked after your user signs out of your app so that they will not be
+ * automatically signed in the next time.
+ *
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ *
+ * @hide
+ */
+ public void clearCredentialSession(
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "executeCreateCredential already canceled");
+ return;
+ }
+
+ ICancellationSignal cancelRemote = null;
+ try {
+ cancelRemote = mService.clearCredentialSession(
+ new ClearCredentialSessionTransport(executor, callback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (cancellationSignal != null && cancelRemote != null) {
+ cancellationSignal.setRemote(cancelRemote);
+ }
+ }
+
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
@@ -184,4 +222,29 @@
() -> mCallback.onError(new CredentialManagerException(errorCode, message)));
}
}
+
+ private static class ClearCredentialSessionTransport
+ extends IClearCredentialSessionCallback.Stub {
+ // TODO: listen for cancellation to release callback.
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
+
+ private ClearCredentialSessionTransport(Executor executor,
+ OutcomeReceiver<Void, CredentialManagerException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onSuccess() {
+ mCallback.onResult(null);
+ }
+
+ @Override
+ public void onError(int errorCode, String message) {
+ mExecutor.execute(
+ () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+ }
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
similarity index 66%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to core/java/android/credentials/IClearCredentialSessionCallback.aidl
index 1550ab3..903e7f5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package android.credentials;
-parcelable RemoteTransitionCompat;
+/**
+ * Listener for clearCredentialSession request.
+ *
+ * @hide
+ */
+interface IClearCredentialSessionCallback {
+ oneway void onSuccess();
+ oneway void onError(int errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index b0f27f9..35688d7 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -18,6 +18,7 @@
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.os.ICancellationSignal;
@@ -32,4 +33,6 @@
@nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
+
+ @nullable ICancellationSignal clearCredentialSession(in IClearCredentialSessionCallback callback, String callingPackage);
}
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 9cc9c72..98157d7 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -34,19 +34,15 @@
public class CreateCredentialProviderData extends ProviderData implements Parcelable {
@NonNull
private final List<Entry> mSaveEntries;
- @NonNull
- private final List<Entry> mActionChips;
private final boolean mIsDefaultProvider;
@Nullable
private final Entry mRemoteEntry;
public CreateCredentialProviderData(
@NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
- @NonNull List<Entry> actionChips, boolean isDefaultProvider,
- @Nullable Entry remoteEntry) {
+ boolean isDefaultProvider, @Nullable Entry remoteEntry) {
super(providerFlattenedComponentName);
mSaveEntries = saveEntries;
- mActionChips = actionChips;
mIsDefaultProvider = isDefaultProvider;
mRemoteEntry = remoteEntry;
}
@@ -56,11 +52,6 @@
return mSaveEntries;
}
- @NonNull
- public List<Entry> getActionChips() {
- return mActionChips;
- }
-
public boolean isDefaultProvider() {
return mIsDefaultProvider;
}
@@ -78,11 +69,6 @@
mSaveEntries = credentialEntries;
AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
- List<Entry> actionChips = new ArrayList<>();
- in.readTypedList(actionChips, Entry.CREATOR);
- mActionChips = actionChips;
- AnnotationValidations.validate(NonNull.class, null, mActionChips);
-
mIsDefaultProvider = in.readBoolean();
Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -93,7 +79,6 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeTypedList(mSaveEntries);
- dest.writeTypedList(mActionChips);
dest.writeBoolean(isDefaultProvider());
dest.writeTypedObject(mRemoteEntry, flags);
}
@@ -124,7 +109,6 @@
public static class Builder {
private @NonNull String mProviderFlattenedComponentName;
private @NonNull List<Entry> mSaveEntries = new ArrayList<>();
- private @NonNull List<Entry> mActionChips = new ArrayList<>();
private boolean mIsDefaultProvider = false;
private @Nullable Entry mRemoteEntry = null;
@@ -140,13 +124,6 @@
return this;
}
- /** Sets the list of action chips to be displayed to the user. */
- @NonNull
- public Builder setActionChips(@NonNull List<Entry> actionChips) {
- mActionChips = actionChips;
- return this;
- }
-
/** Sets whether this provider is the user's selected default provider. */
@NonNull
public Builder setIsDefaultProvider(boolean isDefaultProvider) {
@@ -158,7 +135,7 @@
@NonNull
public CreateCredentialProviderData build() {
return new CreateCredentialProviderData(mProviderFlattenedComponentName,
- mSaveEntries, mActionChips, mIsDefaultProvider, mRemoteEntry);
+ mSaveEntries, mIsDefaultProvider, mRemoteEntry);
}
}
}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 59d5118..c3937b6 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -69,25 +69,24 @@
private final boolean mIsFirstUsage;
- // TODO: change to package name
@NonNull
- private final String mAppDisplayName;
+ private final String mAppPackageName;
/** Creates new {@code RequestInfo} for a create-credential flow. */
public static RequestInfo newCreateRequestInfo(
@NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
- boolean isFirstUsage, @NonNull String appDisplayName) {
+ boolean isFirstUsage, @NonNull String appPackageName) {
return new RequestInfo(
- token, TYPE_CREATE, isFirstUsage, appDisplayName,
+ token, TYPE_CREATE, isFirstUsage, appPackageName,
createCredentialRequest, null);
}
/** Creates new {@code RequestInfo} for a get-credential flow. */
public static RequestInfo newGetRequestInfo(
@NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
- boolean isFirstUsage, @NonNull String appDisplayName) {
+ boolean isFirstUsage, @NonNull String appPackageName) {
return new RequestInfo(
- token, TYPE_GET, isFirstUsage, appDisplayName,
+ token, TYPE_GET, isFirstUsage, appPackageName,
null, getCredentialRequest);
}
@@ -116,8 +115,8 @@
/** Returns the display name of the app that made this request. */
@NonNull
- public String getAppDisplayName() {
- return mAppDisplayName;
+ public String getAppPackageName() {
+ return mAppPackageName;
}
/**
@@ -139,13 +138,13 @@
}
private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
- boolean isFirstUsage, @NonNull String appDisplayName,
+ boolean isFirstUsage, @NonNull String appPackageName,
@Nullable CreateCredentialRequest createCredentialRequest,
@Nullable GetCredentialRequest getCredentialRequest) {
mToken = token;
mType = type;
mIsFirstUsage = isFirstUsage;
- mAppDisplayName = appDisplayName;
+ mAppPackageName = appPackageName;
mCreateCredentialRequest = createCredentialRequest;
mGetCredentialRequest = getCredentialRequest;
}
@@ -154,7 +153,7 @@
IBinder token = in.readStrongBinder();
String type = in.readString8();
boolean isFirstUsage = in.readBoolean();
- String appDisplayName = in.readString8();
+ String appPackageName = in.readString8();
CreateCredentialRequest createCredentialRequest =
in.readTypedObject(CreateCredentialRequest.CREATOR);
GetCredentialRequest getCredentialRequest =
@@ -165,8 +164,8 @@
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
mIsFirstUsage = isFirstUsage;
- mAppDisplayName = appDisplayName;
- AnnotationValidations.validate(NonNull.class, null, mAppDisplayName);
+ mAppPackageName = appPackageName;
+ AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
mCreateCredentialRequest = createCredentialRequest;
mGetCredentialRequest = getCredentialRequest;
}
@@ -176,7 +175,7 @@
dest.writeStrongBinder(mToken);
dest.writeString8(mType);
dest.writeBoolean(mIsFirstUsage);
- dest.writeString8(mAppDisplayName);
+ dest.writeString8(mAppPackageName);
dest.writeTypedObject(mCreateCredentialRequest, flags);
dest.writeTypedObject(mGetCredentialRequest, flags);
}
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 99b58c9..535b551 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -73,6 +73,13 @@
+ ".extra.all_sensors";
/**
+ * An extra containing the sensor type
+ * @hide
+ */
+ public static final String EXTRA_TOGGLE_TYPE = SensorPrivacyManager.class.getName()
+ + ".extra.toggle_type";
+
+ /**
* Sensor constants which are used in {@link SensorPrivacyManager}
*/
public static class Sensors {
diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index b9a327b..d9ee561 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -204,7 +204,7 @@
*
*/
@SuppressLint("MissingGetterMatchingBuilder")
- public @NonNull Builder addOrientationForState(long deviceState, long angle) {
+ public @NonNull Builder addOrientationForState(@DeviceState long deviceState, long angle) {
if (angle % 90 != 0) {
throw new IllegalArgumentException("Sensor orientation not divisible by 90: "
+ angle);
diff --git a/core/java/android/hardware/camera2/params/Face.java b/core/java/android/hardware/camera2/params/Face.java
index 1d9a5a3a..32688a72 100644
--- a/core/java/android/hardware/camera2/params/Face.java
+++ b/core/java/android/hardware/camera2/params/Face.java
@@ -17,6 +17,7 @@
package android.hardware.camera2.params;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Point;
@@ -173,6 +174,7 @@
* @see #SCORE_MAX
* @see #SCORE_MIN
*/
+ @IntRange(from = SCORE_MIN, to = SCORE_MAX)
public int getScore() {
return mScore;
}
@@ -377,7 +379,7 @@
* @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
* @return This builder.
*/
- public @NonNull Builder setScore(int score) {
+ public @NonNull Builder setScore(@IntRange(from = SCORE_MIN, to = SCORE_MAX) int score) {
checkNotUsed();
checkScore(score);
mBuilderFieldsSet |= FIELD_SCORE;
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
deleted file mode 100644
index 9c2aa66..0000000
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.hardware.fingerprint;
-
-/**
- * A listener for the high-brightness mode (HBM) transitions. This allows other components to
- * perform certain actions when the HBM is toggled on or off. For example, a display manager
- * implementation can subscribe to these events from UdfpsController and adjust the display's
- * refresh rate when the HBM is enabled.
- *
- * @hide
- */
-oneway interface IUdfpsHbmListener {
- /**
- * UdfpsController will call this method when the HBM is enabled.
- *
- * @param displayId The displayId for which the HBM is enabled. See
- * {@link android.view.Display#getDisplayId()}.
- */
- void onHbmEnabled(int displayId);
-
- /**
- * UdfpsController will call this method when the HBM is disabled.
- *
- * @param displayId The displayId for which the HBM is disabled. See
- * {@link android.view.Display#getDisplayId()}.
- */
- void onHbmDisabled(int displayId);
-}
-
diff --git a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
new file mode 100644
index 0000000..8587348
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.fingerprint;
+
+/**
+ * A callback for UDFPS refresh rate. This allows other components to
+ * perform certain actions when the refresh rate is enabled or disabled.
+ * For example, a display manager implementation can subscribe to these
+ * events from UdfpsController when refresh rate is enabled or disabled.
+ *
+ * @hide
+ */
+oneway interface IUdfpsRefreshRateRequestCallback {
+ /**
+ * Sets the appropriate display refresh rate for UDFPS.
+ *
+ * @param displayId The displayId for which the refresh rate should be set. See
+ * {@link android.view.Display#getDisplayId()}.
+ */
+ void onRequestEnabled(int displayId);
+
+ /**
+ * Unsets the appropriate display refresh rate for UDFPS.
+ *
+ * @param displayId The displayId for which the refresh rate should be unset. See
+ * {@link android.view.Display#getDisplayId()}.
+ */
+ void onRequestDisabled(int displayId);
+}
+
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 8b71092..59465db 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -251,7 +251,8 @@
Objects.requireNonNull(entry.getValue());
}
}
- mDabFrequencyTable = dabFrequencyTable;
+ mDabFrequencyTable = (dabFrequencyTable == null || dabFrequencyTable.isEmpty())
+ ? null : dabFrequencyTable;
mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo;
}
@@ -446,7 +447,8 @@
mIsBgScanSupported = in.readInt() == 1;
mSupportedProgramTypes = arrayToSet(in.createIntArray());
mSupportedIdentifierTypes = arrayToSet(in.createIntArray());
- mDabFrequencyTable = Utils.readStringIntMap(in);
+ Map<String, Integer> dabFrequencyTableIn = Utils.readStringIntMap(in);
+ mDabFrequencyTable = (dabFrequencyTableIn.isEmpty()) ? null : dabFrequencyTableIn;
mVendorInfo = Utils.readStringMap(in);
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 39d362b..d902486 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1774,16 +1774,13 @@
}
/**
- * Implement to return our standard {@link InputMethodImpl}. Subclasses
- * can override to provide their own customized version.
+ * Implement to return our standard {@link InputMethodImpl}.
*
- * @deprecated IME developers don't need to override this method to get callbacks information.
- * Most methods in {@link InputMethodImpl} have corresponding callbacks.
- * Use {@link InputMethodService#onBindInput()}, {@link InputMethodService#onUnbindInput()},
- * {@link InputMethodService#onWindowShown()}, {@link InputMethodService#onWindowHidden()}, etc.
- *
- * <p>Starting from Android U and later, override this method won't guarantee that IME works
- * as previous platform behavior.</p>
+ * @deprecated Overriding or calling this method is strongly discouraged. A future version of
+ * Android will remove the ability to use this method. Use the callbacks on
+ * {@link InputMethodService} as {@link InputMethodService#onBindInput()},
+ * {@link InputMethodService#onUnbindInput()}, {@link InputMethodService#onWindowShown()},
+ * {@link InputMethodService#onWindowHidden()}, etc.
*/
@Deprecated
@Override
@@ -1792,18 +1789,17 @@
}
/**
- * Implement to return our standard {@link InputMethodSessionImpl}. Subclasses
- * can override to provide their own customized version.
+ * Implement to return our standard {@link InputMethodSessionImpl}.
*
- * @deprecated IME developers don't need to override this method to get callbacks information.
+ * <p>IMEs targeting on Android U and above cannot override this method, or an
+ * {@link LinkageError} would be thrown.</p>
+ *
+ * @deprecated Overriding or calling this method is strongly discouraged.
* Most methods in {@link InputMethodSessionImpl} have corresponding callbacks.
* Use {@link InputMethodService#onFinishInput()},
* {@link InputMethodService#onDisplayCompletions(CompletionInfo[])},
* {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)},
* {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead.
- *
- * <p>IMEs targeting on Android U and above cannot override this method, or an
- * {@link LinkageError} would be thrown.</p>
*/
@Deprecated
@Override
@@ -2272,6 +2268,8 @@
* current input field.
*
* @param id Unique identifier of the new input method to start.
+ * @throws IllegalArgumentException if the input method is unknown or filtered
+ * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
*/
public void switchInputMethod(String id) {
mPrivOps.setInputMethod(id);
@@ -2284,6 +2282,8 @@
*
* @param id Unique identifier of the new input method to start.
* @param subtype The new subtype of the new input method to be switched to.
+ * @throws IllegalArgumentException if the input method is unknown or filtered
+ * by the rules of <a href="/training/basics/intents/package-visibility">package visibility</a>.
*/
public final void switchInputMethod(String id, InputMethodSubtype subtype) {
mPrivOps.setInputMethodAndSubtype(id, subtype);
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 933769a..8eaa5ad 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -69,6 +69,7 @@
boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
UserInfo getProfileParent(int userId);
boolean isSameProfileGroup(int userId, int otherUserHandle);
+ boolean isHeadlessSystemUserMode();
boolean isUserOfType(int userId, in String userType);
@UnsupportedAppUsage
UserInfo getUserInfo(int userId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d84037f..9d8df7e 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -70,3 +70,7 @@
# Tracing
per-file Trace.java = file:/TRACE_OWNERS
+
+# PermissionEnforcer
+per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
+per-file PermissionEnforcer.java = file:/core/java/android/permission/OWNERS
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index f1879dd..3d20d63 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -91,7 +91,6 @@
public class UserManager {
private static final String TAG = "UserManager";
- private static final boolean VERBOSE = false;
@UnsupportedAppUsage
private final IUserManager mService;
@@ -104,6 +103,9 @@
/** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */
private String mProfileTypeOfProcessUser = null;
+ /** Whether the device is in headless system user mode; null until cached. */
+ private static Boolean sIsHeadlessSystemUser = null;
+
/**
* User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
* This type of user cannot be created; it can only pre-exist on first boot.
@@ -2068,28 +2070,20 @@
* @return whether the device is running in a headless system user mode.
*/
public static boolean isHeadlessSystemUserMode() {
- final boolean realMode = RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
- if (!Build.isDebuggable()) {
- return realMode;
+ // No need for synchronization. Once it becomes non-null, it'll be non-null forever.
+ // (Its value is determined when UMS is constructed and cannot change.)
+ // Worst case we might end up calling the AIDL method multiple times but that's fine.
+ if (sIsHeadlessSystemUser == null) {
+ // Unfortunately this API is static, but the property no longer is. So go fetch the UMS.
+ try {
+ final IUserManager service = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ sIsHeadlessSystemUser = service.isHeadlessSystemUserMode();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
}
-
- final String emulatedMode = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
- switch (emulatedMode) {
- case SYSTEM_USER_MODE_EMULATION_FULL:
- if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as false");
- return false;
- case SYSTEM_USER_MODE_EMULATION_HEADLESS:
- if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as true");
- return true;
- case SYSTEM_USER_MODE_EMULATION_DEFAULT:
- case "": // property not set
- return realMode;
- default:
- Log.wtf(TAG, "isHeadlessSystemUserMode(): invalid value of property "
- + SYSTEM_USER_MODE_EMULATION_PROPERTY + " (" + emulatedMode + "); using"
- + " default value (headless=" + realMode + ")");
- return realMode;
- }
+ return sIsHeadlessSystemUser;
}
/**
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 3bf9ca0..b117a9a 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -16,7 +16,9 @@
package android.preference;
+import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.app.NotificationManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
@@ -35,6 +37,7 @@
import android.os.HandlerThread;
import android.os.Message;
import android.preference.VolumePreference.VolumeStore;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.System;
@@ -44,6 +47,7 @@
import android.widget.SeekBar.OnSeekBarChangeListener;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.os.SomeArgs;
import java.util.concurrent.TimeUnit;
@@ -115,7 +119,6 @@
private final int mMaxStreamVolume;
private boolean mAffectedByRingerMode;
private boolean mNotificationOrRing;
- private final boolean mNotifAliasRing;
private final Receiver mReceiver = new Receiver();
private Handler mHandler;
@@ -158,6 +161,7 @@
this(context, streamType, defaultUri, callback, true /* playSample */);
}
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public SeekBarVolumizer(
Context context,
int streamType,
@@ -180,8 +184,6 @@
if (mNotificationOrRing) {
mRingerMode = mAudioManager.getRingerModeInternal();
}
- mNotifAliasRing = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_alias_ring_notif_stream_types);
mZenMode = mNotificationManager.getZenMode();
if (hasAudioProductStrategies()) {
@@ -288,7 +290,9 @@
* so that when user attempts to slide the notification seekbar out of vibrate the
* seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
*/
- if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING
+ if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ || mStreamType == AudioManager.STREAM_RING
|| (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
mSeekBar.setProgress(0, true);
}
@@ -365,7 +369,9 @@
// set the time of stop volume
if ((mStreamType == AudioManager.STREAM_VOICE_CALL
|| mStreamType == AudioManager.STREAM_RING
- || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
+ || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ && mStreamType == AudioManager.STREAM_NOTIFICATION)
|| mStreamType == AudioManager.STREAM_ALARM)) {
sStopVolumeTime = java.lang.System.currentTimeMillis();
}
@@ -644,8 +650,10 @@
}
private void updateVolumeSlider(int streamType, int streamValue) {
- final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
- ? isNotificationOrRing(streamType) : streamType == mStreamType;
+ final boolean streamMatch = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+ && mNotificationOrRing ? isNotificationOrRing(streamType) :
+ streamType == mStreamType;
if (mSeekBar != null && streamMatch && streamValue != -1) {
final boolean muted = mAudioManager.isStreamMute(mStreamType)
|| streamValue == 0;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 897b7c3..fab6f7b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9876,6 +9876,13 @@
"fingerprint_side_fps_auth_downtime";
/**
+ * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+ * @hide
+ */
+ public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
+ "sfps_require_screen_on_to_auth_enabled";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index 559b1ca..e330d1e 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -35,18 +35,22 @@
public final class CreateCredentialResponse implements Parcelable {
private final @Nullable CharSequence mHeader;
private final @NonNull List<SaveEntry> mSaveEntries;
+ private final @Nullable Action mRemoteSaveEntry;
+ //TODO : Add actions if needed
private CreateCredentialResponse(@NonNull Parcel in) {
mHeader = in.readCharSequence();
List<SaveEntry> saveEntries = new ArrayList<>();
in.readTypedList(saveEntries, SaveEntry.CREATOR);
mSaveEntries = saveEntries;
+ mRemoteSaveEntry = in.readTypedObject(Action.CREATOR);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeCharSequence(mHeader);
dest.writeTypedList(mSaveEntries);
+ dest.writeTypedObject(mRemoteSaveEntry, flags);
}
@Override
@@ -69,11 +73,13 @@
/* package-private */ CreateCredentialResponse(
@Nullable CharSequence header,
- @NonNull List<SaveEntry> saveEntries) {
+ @NonNull List<SaveEntry> saveEntries,
+ @Nullable Action remoteSaveEntry) {
this.mHeader = header;
this.mSaveEntries = saveEntries;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mSaveEntries);
+ this.mRemoteSaveEntry = remoteSaveEntry;
}
/** Returns the header to be displayed on the UI. */
@@ -86,6 +92,11 @@
return mSaveEntries;
}
+ /** Returns the remote save entry to be displayed on the UI. */
+ public @NonNull Action getRemoteSaveEntry() {
+ return mRemoteSaveEntry;
+ }
+
/**
* A builder for {@link CreateCredentialResponse}
*/
@@ -94,6 +105,7 @@
private @Nullable CharSequence mHeader;
private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
+ private @Nullable Action mRemoteSaveEntry;
/** Sets the header to be displayed on the UI. */
public @NonNull Builder setHeader(@Nullable CharSequence header) {
@@ -126,6 +138,14 @@
}
/**
+ * Sets a remote save entry to be shown on the UI.
+ */
+ public @NonNull Builder setRemoteSaveEntry(@Nullable Action remoteSaveEntry) {
+ mRemoteSaveEntry = remoteSaveEntry;
+ return this;
+ }
+
+ /**
* Builds the instance.
*
* @throws IllegalArgumentException If {@code saveEntries} is empty.
@@ -135,7 +155,8 @@
+ "not be empty");
return new CreateCredentialResponse(
mHeader,
- mSaveEntries);
+ mSaveEntries,
+ mRemoteSaveEntry);
}
}
}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index 2cce169..ab5b524 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -43,12 +43,17 @@
/** List of provider actions to be displayed on the UI. */
private final @NonNull List<Action> mActions;
+ /** Remote credential entry to get the response from a different device. */
+ private final @Nullable Action mRemoteCredentialEntry;
+
private CredentialsDisplayContent(@Nullable CharSequence header,
@NonNull List<CredentialEntry> credentialEntries,
- @NonNull List<Action> actions) {
+ @NonNull List<Action> actions,
+ @Nullable Action remoteCredentialEntry) {
mHeader = header;
mCredentialEntries = credentialEntries;
mActions = actions;
+ mRemoteCredentialEntry = remoteCredentialEntry;
}
private CredentialsDisplayContent(@NonNull Parcel in) {
@@ -59,6 +64,7 @@
List<Action> actions = new ArrayList<>();
in.readTypedList(actions, Action.CREATOR);
mActions = actions;
+ mRemoteCredentialEntry = in.readTypedObject(Action.CREATOR);
}
public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
@@ -84,6 +90,7 @@
dest.writeCharSequence(mHeader);
dest.writeTypedList(mCredentialEntries, flags);
dest.writeTypedList(mActions, flags);
+ dest.writeTypedObject(mRemoteCredentialEntry, flags);
}
/**
@@ -108,12 +115,20 @@
}
/**
+ * Returns the remote credential entry to be displayed on the UI.
+ */
+ public @Nullable Action getRemoteCredentialEntry() {
+ return mRemoteCredentialEntry;
+ }
+
+ /**
* Builds an instance of {@link CredentialsDisplayContent}.
*/
public static final class Builder {
- private CharSequence mHeader = null;
+ private CharSequence mHeader;
private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
private List<Action> mActions = new ArrayList<>();
+ private Action mRemoteCredentialEntry;
/**
* Sets the header to be displayed on the UI.
@@ -124,6 +139,14 @@
}
/**
+ * Sets the remote credential entry to be displayed on the UI.
+ */
+ public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
+ mRemoteCredentialEntry = remoteCredentialEntry;
+ return this;
+ }
+
+ /**
* Adds a {@link CredentialEntry} to the list of entries to be displayed on
* the UI.
*
@@ -185,7 +208,8 @@
throw new IllegalStateException("credentialEntries and actions must not both "
+ "be empty");
}
- return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions);
+ return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions,
+ mRemoteCredentialEntry);
}
}
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 1285d1e..7c125c7 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -349,7 +349,7 @@
* {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
* AlwaysOnHotwordDetector.Callback)} or {@link #createHotwordDetector(PersistableBundle,
* SharedMemory, HotwordDetector.Callback)}, call this will throw an
- * {@link IllegalArgumentException}.
+ * {@link IllegalStateException}.
*
* @param keyphrase The keyphrase that's being used, for example "Hello Android".
* @param locale The locale for which the enrollment needs to be performed.
@@ -385,7 +385,7 @@
*
* <p>Note: If there are any active detectors that are created by using
* {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
- * call this will throw an {@link IllegalArgumentException}.
+ * call this will throw an {@link IllegalStateException}.
*
* @param keyphrase The keyphrase that's being used, for example "Hello Android".
* @param locale The locale for which the enrollment needs to be performed.
@@ -428,13 +428,18 @@
if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
// Allow only one concurrent recognition via the APIs.
safelyShutdownAllHotwordDetectors();
- }
-
- for (HotwordDetector detector : mActiveHotwordDetectors) {
- if (detector.isUsingHotwordDetectionService() != supportHotwordDetectionService) {
- throw new IllegalArgumentException(
- "It disallows to create trusted and non-trusted detectors "
- + "at the same time.");
+ } else {
+ for (HotwordDetector detector : mActiveHotwordDetectors) {
+ if (detector.isUsingHotwordDetectionService()
+ != supportHotwordDetectionService) {
+ throw new IllegalStateException(
+ "It disallows to create trusted and non-trusted detectors "
+ + "at the same time.");
+ } else if (detector instanceof AlwaysOnHotwordDetector) {
+ throw new IllegalStateException(
+ "There is already an active AlwaysOnHotwordDetector. "
+ + "It must be destroyed to create a new one.");
+ }
}
}
@@ -442,11 +447,7 @@
callback, mKeyphraseEnrollmentInfo, mSystemService,
getApplicationContext().getApplicationInfo().targetSdkVersion,
supportHotwordDetectionService);
- if (!mActiveHotwordDetectors.add(dspDetector)) {
- throw new IllegalArgumentException(
- "the keyphrase=" + keyphrase + " and locale=" + locale
- + " are already used by another always-on detector");
- }
+ mActiveHotwordDetectors.add(dspDetector);
try {
dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
@@ -480,7 +481,7 @@
*
* <p>Note: If there are any active detectors that are created by using
* {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
- * call this will throw an {@link IllegalArgumentException}.
+ * call this will throw an {@link IllegalStateException}.
*
* @param options Application configuration data to be provided to the
* {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
@@ -513,11 +514,11 @@
} else {
for (HotwordDetector detector : mActiveHotwordDetectors) {
if (!detector.isUsingHotwordDetectionService()) {
- throw new IllegalArgumentException(
+ throw new IllegalStateException(
"It disallows to create trusted and non-trusted detectors "
+ "at the same time.");
} else if (detector instanceof SoftwareHotwordDetector) {
- throw new IllegalArgumentException(
+ throw new IllegalStateException(
"There is already an active SoftwareHotwordDetector. "
+ "It must be destroyed to create a new one.");
}
@@ -527,6 +528,7 @@
SoftwareHotwordDetector softwareHotwordDetector =
new SoftwareHotwordDetector(
mSystemService, null, callback);
+ mActiveHotwordDetectors.add(softwareHotwordDetector);
try {
softwareHotwordDetector.registerOnDestroyListener(
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 1148120..6f8c5db 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -224,7 +224,7 @@
byte[] resultBytes = messageDigest.digest();
if (separator == null) {
- return HexEncoding.encodeToString(resultBytes, true);
+ return HexEncoding.encodeToString(resultBytes, false);
}
int length = resultBytes.length;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index a08a5a8..bf40bdc 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1779,16 +1779,18 @@
* @param source The source of this event.
* @param displayId The display ID associated with this event.
* @param flags The motion event flags.
+ * @param classification The classification to give this event.
* @hide
*/
- static public MotionEvent obtain(long downTime, long eventTime,
+ public static MotionEvent obtain(long downTime, long eventTime,
int action, int pointerCount, PointerProperties[] pointerProperties,
PointerCoords[] pointerCoords, int metaState, int buttonState,
float xPrecision, float yPrecision, int deviceId,
- int edgeFlags, int source, int displayId, int flags) {
+ int edgeFlags, int source, int displayId, int flags,
+ @Classification int classification) {
MotionEvent ev = obtain();
final boolean success = ev.initialize(deviceId, source, displayId, action, flags, edgeFlags,
- metaState, buttonState, CLASSIFICATION_NONE, 0, 0, xPrecision, yPrecision,
+ metaState, buttonState, classification, 0, 0, xPrecision, yPrecision,
downTime * NS_PER_MS, eventTime * NS_PER_MS,
pointerCount, pointerProperties, pointerCoords);
if (!success) {
@@ -1825,6 +1827,46 @@
* @param edgeFlags A bitfield indicating which edges, if any, were touched by this
* MotionEvent.
* @param source The source of this event.
+ * @param displayId The display ID associated with this event.
+ * @param flags The motion event flags.
+ * @hide
+ */
+ public static MotionEvent obtain(long downTime, long eventTime,
+ int action, int pointerCount, PointerProperties[] pointerProperties,
+ PointerCoords[] pointerCoords, int metaState, int buttonState,
+ float xPrecision, float yPrecision, int deviceId,
+ int edgeFlags, int source, int displayId, int flags) {
+ return obtain(downTime, eventTime, action, pointerCount, pointerProperties, pointerCoords,
+ metaState, buttonState, xPrecision, yPrecision, deviceId, edgeFlags, source,
+ displayId, flags, CLASSIFICATION_NONE);
+ }
+
+ /**
+ * Create a new MotionEvent, filling in all of the basic values that
+ * define the motion.
+ *
+ * @param downTime The time (in ms) when the user originally pressed down to start
+ * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param eventTime The the time (in ms) when this specific event was generated. This
+ * must be obtained from {@link SystemClock#uptimeMillis()}.
+ * @param action The kind of action being performed, such as {@link #ACTION_DOWN}.
+ * @param pointerCount The number of pointers that will be in this event.
+ * @param pointerProperties An array of <em>pointerCount</em> values providing
+ * a {@link PointerProperties} property object for each pointer, which must
+ * include the pointer identifier.
+ * @param pointerCoords An array of <em>pointerCount</em> values providing
+ * a {@link PointerCoords} coordinate object for each pointer.
+ * @param metaState The state of any meta / modifier keys that were in effect when
+ * the event was generated.
+ * @param buttonState The state of buttons that are pressed.
+ * @param xPrecision The precision of the X coordinate being reported.
+ * @param yPrecision The precision of the Y coordinate being reported.
+ * @param deviceId The id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device; other
+ * numbers are arbitrary and you shouldn't depend on the values.
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
+ * MotionEvent.
+ * @param source The source of this event.
* @param flags The motion event flags.
*/
public static MotionEvent obtain(long downTime, long eventTime,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 49d9e67..2b071db 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17434,7 +17434,7 @@
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
- protected void dispatchDraw(Canvas canvas) {
+ protected void dispatchDraw(@NonNull Canvas canvas) {
}
@@ -20719,7 +20719,7 @@
out.bottom = mScrollY + mBottom - mTop;
}
- private void onDrawScrollIndicators(Canvas c) {
+ private void onDrawScrollIndicators(@NonNull Canvas c) {
if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) {
// No scroll indicators enabled.
return;
@@ -20903,7 +20903,7 @@
*
* @see #awakenScrollBars(int)
*/
- protected final void onDrawScrollBars(Canvas canvas) {
+ protected final void onDrawScrollBars(@NonNull Canvas canvas) {
// scrollbars are drawn only when the animation is running
final ScrollabilityCache cache = mScrollCache;
@@ -21015,7 +21015,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
+ protected void onDrawHorizontalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
int l, int t, int r, int b) {
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
@@ -21035,7 +21035,7 @@
* @hide
*/
@UnsupportedAppUsage
- protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+ protected void onDrawVerticalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
int l, int t, int r, int b) {
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
@@ -21046,7 +21046,7 @@
*
* @param canvas the canvas on which the background will be drawn
*/
- protected void onDraw(Canvas canvas) {
+ protected void onDraw(@NonNull Canvas canvas) {
}
/*
@@ -23161,7 +23161,7 @@
*
* @hide
*/
- protected final boolean drawsWithRenderNode(Canvas canvas) {
+ protected final boolean drawsWithRenderNode(@NonNull Canvas canvas) {
return mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& canvas.isHardwareAccelerated();
@@ -23173,7 +23173,7 @@
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
- boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+ boolean draw(@NonNull Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
@@ -23461,7 +23461,7 @@
return (int) (dips * scale + 0.5f);
}
- final private void debugDrawFocus(Canvas canvas) {
+ private void debugDrawFocus(@NonNull Canvas canvas) {
if (isFocused()) {
final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
final int l = mScrollX;
@@ -23496,7 +23496,7 @@
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
- public void draw(Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
@@ -23731,7 +23731,7 @@
* @param canvas Canvas on which to draw the background
*/
@UnsupportedAppUsage
- private void drawBackground(Canvas canvas) {
+ private void drawBackground(@NonNull Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
@@ -24631,7 +24631,7 @@
* Draw the default focus highlight onto the canvas if there is one and this view is focused.
* @param canvas the canvas where we're drawing the highlight.
*/
- private void drawDefaultFocusHighlight(Canvas canvas) {
+ private void drawDefaultFocusHighlight(@NonNull Canvas canvas) {
if (mDefaultFocusHighlight != null && isFocused()) {
if (mDefaultFocusHighlightSizeChanged) {
mDefaultFocusHighlightSizeChanged = false;
@@ -25429,7 +25429,7 @@
*
* @param canvas canvas to draw into
*/
- public void onDrawForeground(Canvas canvas) {
+ public void onDrawForeground(@NonNull Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
@@ -27487,7 +27487,7 @@
*
* @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
*/
- public void onDrawShadow(Canvas canvas) {
+ public void onDrawShadow(@NonNull Canvas canvas) {
final View view = mView.get();
if (view != null) {
view.draw(canvas);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c4f20dc..46b2cfc 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4166,7 +4166,7 @@
/**
* @hide
*/
- protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
+ protected void onDebugDrawMargins(@NonNull Canvas canvas, Paint paint) {
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
c.getLayoutParams().onDebugDraw(c, canvas, paint);
@@ -4176,7 +4176,7 @@
/**
* @hide
*/
- protected void onDebugDraw(Canvas canvas) {
+ protected void onDebugDraw(@NonNull Canvas canvas) {
Paint paint = getDebugPaint();
// Draw optical bounds
@@ -4224,7 +4224,7 @@
}
@Override
- protected void dispatchDraw(Canvas canvas) {
+ protected void dispatchDraw(@NonNull Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
@@ -4533,7 +4533,7 @@
* @param drawingTime The time at which draw is occurring
* @return True if an invalidate() was issued
*/
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
@@ -9208,7 +9208,8 @@
}
}
- private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+ private static void drawRect(@NonNull Canvas canvas, Paint paint, int x1, int y1,
+ int x2, int y2) {
if (sDebugLines== null) {
// TODO: This won't work with multiple UI threads in a single process
sDebugLines = new float[16];
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index e8b1b46..a66c67b 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -346,14 +346,13 @@
@AnyThread
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
- static void showInputMethodPickerFromSystem(@NonNull IInputMethodClient client,
- int auxiliarySubtypeMode, int displayId) {
+ static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
final IInputMethodManager service = getService();
if (service == null) {
return;
}
try {
- service.showInputMethodPickerFromSystem(client, auxiliarySubtypeMode, displayId);
+ service.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 514d909..74afced 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -126,6 +126,7 @@
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
@@ -3114,6 +3115,8 @@
* when it was started, which allows it to perform this operation on
* itself.
* @param id The unique identifier for the new input method to be switched to.
+ * @throws IllegalArgumentException if the input method is unknown or filtered by the rules of
+ * <a href="/training/basics/intents/package-visibility">package visibility</a>.
* @deprecated Use {@link InputMethodService#switchInputMethod(String)}
* instead. This method was intended for IME developers who should be accessing APIs through
* the service. APIs in this class are intended for app developers interacting with the IME.
@@ -3184,6 +3187,8 @@
* itself.
* @param id The unique identifier for the new input method to be switched to.
* @param subtype The new subtype of the new input method to be switched to.
+ * @throws IllegalArgumentException if the input method is unknown or filtered by the rules of
+ * <a href="/training/basics/intents/package-visibility">package visibility</a>.
* @deprecated Use
* {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}
* instead. This method was intended for IME developers who should be accessing APIs through
@@ -3462,7 +3467,7 @@
final int mode = showAuxiliarySubtypes
? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
: SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
- IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mClient, mode, displayId);
+ IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mode, displayId);
}
@GuardedBy("mH")
@@ -3639,6 +3644,32 @@
}
/**
+ * {@code true} means that
+ * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
+ * {@code false} when the IME client and the IME run in different displays.
+ */
+ final AtomicBoolean mRequestCursorUpdateDisplayIdCheck = new AtomicBoolean(true);
+
+ /**
+ * Controls the display ID mismatch validation in
+ * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)}.
+ *
+ * <p>{@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} is not guaranteed to work
+ * correctly when the IME client and the IME run in different displays. This is why
+ * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
+ * {@code false} by default when the display ID does not match. This method allows special apps
+ * to override this behavior when they are sure that it should work.</p>
+ *
+ * <p>By default the validation is enabled.</p>
+ *
+ * @param enabled {@code false} to disable the display ID validation.
+ * @hide
+ */
+ public void setRequestCursorUpdateDisplayIdCheck(boolean enabled) {
+ mRequestCursorUpdateDisplayIdCheck.set(enabled);
+ }
+
+ /**
* An internal API for {@link android.hardware.display.VirtualDisplay} to report where its
* embedded virtual display is placed.
*
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index ead7924..c01c310 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -1015,27 +1015,6 @@
});
}
- /**
- * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
- *
- * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
- * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)}
- * @param cursorUpdateFilter the filter for
- * {@link InputConnection#requestCursorUpdates(int, int)}
- * @param imeDisplayId displayId on which IME is displayed.
- */
- @Dispatching(cancellable = true)
- public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter,
- int imeDisplayId) {
- final int currentSessionId = mCurrentSessionId.get();
- dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
- if (currentSessionId != mCurrentSessionId.get()) {
- return; // cancelled
- }
- requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
- });
- }
-
@Dispatching(cancellable = true)
@Override
public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
@@ -1071,7 +1050,8 @@
Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
return false;
}
- if (mParentInputMethodManager.getDisplayId() != imeDisplayId
+ if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
+ && mParentInputMethodManager.getDisplayId() != imeDisplayId
&& !mParentInputMethodManager.hasVirtualDisplayToScreenMatrix()) {
// requestCursorUpdates() is not currently supported across displays.
return false;
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 5e74381..510a92d 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -116,9 +116,7 @@
* <p class="note">ListView attempts to reuse view objects in order to improve performance and
* avoid a lag in response to user scrolls. To take advantage of this feature, check if the
* {@code convertView} provided to {@code getView(...)} is null before creating or inflating a new
- * view object. See
- * <a href="{@docRoot}training/improving-layouts/smooth-scrolling.html">
- * Making ListView Scrolling Smooth</a> for more ways to ensure a smooth user experience.</p>
+ * view object.</p>
*
* <p>To specify an action when a user clicks or taps on a single list item, see
* <a href="{@docRoot}guide/topics/ui/declaring-layout.html#HandlingUserSelections">
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 36eaf49..57e0ce8 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -105,4 +105,7 @@
/** @return An interface enabling the transition players to report its metrics. */
ITransitionMetricsReporter getTransitionMetricsReporter();
+
+ /** @return The transaction queue token used by WM. */
+ IBinder getApplyToken();
}
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 2a80d02..930aaa2 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -26,6 +26,7 @@
import android.os.RemoteException;
import android.util.Singleton;
import android.view.RemoteAnimationAdapter;
+import android.view.SurfaceControl;
/**
* Base class for organizing specific types of windows like Tasks and DisplayAreas
@@ -184,6 +185,26 @@
}
}
+ /**
+ * Use WM's transaction-queue instead of Shell's independent one. This is necessary
+ * if WM and Shell need to coordinate transactions (eg. for shell transitions).
+ * @return true if successful, false otherwise.
+ * @hide
+ */
+ public boolean shareTransactionQueue() {
+ final IBinder wmApplyToken;
+ try {
+ wmApplyToken = getWindowOrganizerController().getApplyToken();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (wmApplyToken == null) {
+ return false;
+ }
+ SurfaceControl.Transaction.setDefaultApplyToken(wmApplyToken);
+ return true;
+ }
+
static IWindowOrganizerController getWindowOrganizerController() {
return IWindowOrganizerControllerSingleton.get();
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 822393f..e926605 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1098,6 +1098,9 @@
@Override // ResolverListCommunicator
public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing,
boolean rebuildCompleted) {
+ if (isDestroyed()) {
+ return;
+ }
if (isAutolaunching()) {
return;
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 44997b4..0f64f6d 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -556,6 +556,11 @@
"show_stop_button_for_user_allowlisted_apps";
/**
+ * (boolean) Whether to show notification volume control slider separate from ring.
+ */
+ public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
+
+ /**
* (boolean) Whether the clipboard overlay is enabled.
*/
public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
new file mode 100644
index 0000000..7571073
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/Counter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.expresslog;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Counter encapsulates StatsD write API calls */
+public final class Counter {
+
+ // Not instantiable.
+ private Counter() {}
+
+ /**
+ * Increments Telemetry Express Counter metric by 1
+ * @hide
+ */
+ public static void logIncrement(@NonNull String metricId) {
+ logIncrement(metricId, 1);
+ }
+
+ /**
+ * Increments Telemetry Express Counter metric by arbitrary value
+ * @hide
+ */
+ public static void logIncrement(@NonNull String metricId, long amount) {
+ final long metricIdHash = hashString(metricId);
+ FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
+ }
+
+ private static native long hashString(String stringToHash);
+}
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
index 2a242a5..edd74f6 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingServerImpl.java
@@ -138,7 +138,7 @@
private void writeTracesToFilesLocked() {
try {
long timeOffsetNs =
- TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(), TimeUnit.NANOSECONDS)
+ TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- SystemClock.elapsedRealtimeNanos();
ProtoOutputStream clientsProto = new ProtoOutputStream();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
similarity index 80%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
rename to core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
index 1550ab3..18bd6e5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package com.android.internal.inputmethod;
-parcelable RemoteTransitionCompat;
+parcelable InputMethodSubtypeHandle;
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
new file mode 100644
index 0000000..780c637
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.security.InvalidParameterException;
+import java.util.Objects;
+
+/**
+ * A stable and serializable identifier for the pair of {@link InputMethodInfo#getId()} and
+ * {@link android.view.inputmethod.InputMethodSubtype}.
+ *
+ * <p>To save {@link InputMethodSubtypeHandle} to storage, call {@link #toStringHandle()} to get a
+ * {@link String} handle and just save it. Once you load a {@link String} handle, you can obtain a
+ * {@link InputMethodSubtypeHandle} instance from {@link #of(String)}.</p>
+ *
+ * <p>For better readability, consider specifying {@link RawHandle} annotation to {@link String}
+ * object when it is a raw {@link String} handle.</p>
+ */
+public final class InputMethodSubtypeHandle implements Parcelable {
+ private static final String SUBTYPE_TAG = "subtype";
+ private static final char DATA_SEPARATOR = ':';
+
+ /**
+ * Can be used to annotate {@link String} object if it is raw handle format.
+ */
+ @Retention(SOURCE)
+ @Target({ElementType.METHOD, ElementType.FIELD, ElementType.LOCAL_VARIABLE,
+ ElementType.PARAMETER})
+ public @interface RawHandle {
+ }
+
+ /**
+ * The main content of this {@link InputMethodSubtypeHandle}. Is designed to be safe to be
+ * saved into storage.
+ */
+ @RawHandle
+ private final String mHandle;
+
+ /**
+ * Encode {@link InputMethodInfo} and {@link InputMethodSubtype#hashCode()} into
+ * {@link RawHandle}.
+ *
+ * @param imeId {@link InputMethodInfo#getId()} to be used.
+ * @param subtypeHashCode {@link InputMethodSubtype#hashCode()} to be used.
+ * @return The encoded {@link RawHandle} string.
+ */
+ @AnyThread
+ @RawHandle
+ @NonNull
+ private static String encodeHandle(@NonNull String imeId, int subtypeHashCode) {
+ return imeId + DATA_SEPARATOR + SUBTYPE_TAG + DATA_SEPARATOR + subtypeHashCode;
+ }
+
+ private InputMethodSubtypeHandle(@NonNull String handle) {
+ mHandle = handle;
+ }
+
+ /**
+ * Creates {@link InputMethodSubtypeHandle} from {@link InputMethodInfo} and
+ * {@link InputMethodSubtype}.
+ *
+ * @param imi {@link InputMethodInfo} to be used.
+ * @param subtype {@link InputMethodSubtype} to be used.
+ * @return A {@link InputMethodSubtypeHandle} object.
+ */
+ @AnyThread
+ @NonNull
+ public static InputMethodSubtypeHandle of(
+ @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+ final int subtypeHashCode =
+ subtype != null ? subtype.hashCode() : InputMethodSubtype.SUBTYPE_ID_NONE;
+ return new InputMethodSubtypeHandle(encodeHandle(imi.getId(), subtypeHashCode));
+ }
+
+ /**
+ * Creates {@link InputMethodSubtypeHandle} from a {@link RawHandle} {@link String}, which can
+ * be obtained by {@link #toStringHandle()}.
+ *
+ * @param stringHandle {@link RawHandle} {@link String} to be parsed.
+ * @return A {@link InputMethodSubtypeHandle} object.
+ * @throws NullPointerException when {@code stringHandle} is {@code null}
+ * @throws InvalidParameterException when {@code stringHandle} is not a valid {@link RawHandle}.
+ */
+ @AnyThread
+ @NonNull
+ public static InputMethodSubtypeHandle of(@RawHandle @NonNull String stringHandle) {
+ final SimpleStringSplitter splitter = new SimpleStringSplitter(DATA_SEPARATOR);
+ splitter.setString(Objects.requireNonNull(stringHandle));
+ if (!splitter.hasNext()) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ final String imeId = splitter.next();
+ final ComponentName componentName = ComponentName.unflattenFromString(imeId);
+ if (componentName == null) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ // TODO: Consolidate IME ID validation logic into one place.
+ if (!Objects.equals(componentName.flattenToShortString(), imeId)) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ if (!splitter.hasNext()) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ final String source = splitter.next();
+ if (!Objects.equals(source, SUBTYPE_TAG)) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ if (!splitter.hasNext()) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ final String hashCodeStr = splitter.next();
+ if (splitter.hasNext()) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+ final int subtypeHashCode;
+ try {
+ subtypeHashCode = Integer.parseInt(hashCodeStr);
+ } catch (NumberFormatException ignore) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+
+ // Redundant expressions (e.g. "0001" instead of "1") are not allowed.
+ if (!Objects.equals(encodeHandle(imeId, subtypeHashCode), stringHandle)) {
+ throw new InvalidParameterException("Invalid handle=" + stringHandle);
+ }
+
+ return new InputMethodSubtypeHandle(stringHandle);
+ }
+
+ /**
+ * @return {@link ComponentName} of the input method.
+ * @see InputMethodInfo#getComponent()
+ */
+ @AnyThread
+ @NonNull
+ public ComponentName getComponentName() {
+ return ComponentName.unflattenFromString(getImeId());
+ }
+
+ /**
+ * @return IME ID.
+ * @see InputMethodInfo#getId()
+ */
+ @AnyThread
+ @NonNull
+ public String getImeId() {
+ return mHandle.substring(0, mHandle.indexOf(DATA_SEPARATOR));
+ }
+
+ /**
+ * @return {@link RawHandle} {@link String} data that should be stable and persistable.
+ * @see #of(String)
+ */
+ @RawHandle
+ @AnyThread
+ @NonNull
+ public String toStringHandle() {
+ return mHandle;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof InputMethodSubtypeHandle)) {
+ return false;
+ }
+ final InputMethodSubtypeHandle that = (InputMethodSubtypeHandle) obj;
+ return Objects.equals(mHandle, that.mHandle);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(mHandle);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @NonNull
+ @Override
+ public String toString() {
+ return "InputMethodSubtypeHandle{mHandle=" + mHandle + "}";
+ }
+
+ /**
+ * {@link Creator} for parcelable.
+ */
+ public static final Creator<InputMethodSubtypeHandle> CREATOR = new Creator<>() {
+ @Override
+ public InputMethodSubtypeHandle createFromParcel(Parcel in) {
+ return of(in.readString8());
+ }
+
+ @Override
+ public InputMethodSubtypeHandle[] newArray(int size) {
+ return new InputMethodSubtypeHandle[size];
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @AnyThread
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(toStringHandle());
+ }
+}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 98d81c9..6870d09 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -51,6 +51,15 @@
// ------ ro.fw.* ------------ //
public static final boolean FW_SYSTEM_USER_SPLIT =
SystemProperties.getBoolean("ro.fw.system_user_split", false);
+ /**
+ * Indicates whether the device should run in headless system user mode,
+ * in which user 0 only runs the system, not a real user.
+ * <p>WARNING about changing this value during an non-wiping update (OTA):
+ * <li>If this value is modified via an update, the change will have no effect, since an
+ * already-existing system user cannot change its mode.
+ * <li>Changing this value during an OTA from a pre-R device is not permitted; attempting to
+ * do so will corrupt the system user.
+ */
public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER =
SystemProperties.getBoolean("ro.fw.mu.headless_system_user", false);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 1d4b246..edbdc86 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -23,13 +23,12 @@
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.service.notification.StatusBarNotification;
-import android.view.InsetsVisibilities;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
@@ -175,9 +174,9 @@
void setBiometicContextListener(in IBiometricContextListener listener);
/**
- * Sets an instance of IUdfpsHbmListener for UdfpsController.
+ * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController.
*/
- void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+ void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
/**
* Notifies System UI that the display is ready to show system decorations.
@@ -201,13 +200,13 @@
* stacks.
* @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME.
* @param behavior the behavior of the focused window.
- * @param requestedVisibilities the collection of the requested visibilities of system insets.
+ * @param requestedVisibleTypes the collection of insets types requested visible.
* @param packageName the package name of the focused app.
* @param letterboxDetails a set of letterbox details of apps visible on the screen.
*/
void onSystemBarAttributesChanged(int displayId, int appearance,
in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- int behavior, in InsetsVisibilities requestedVisibilities, String packageName,
+ int behavior, int requestedVisibleTypes, String packageName,
in LetterboxDetails[] letterboxDetails);
/**
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ef8f2db..d190681 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -23,7 +23,7 @@
import android.hardware.biometrics.IBiometricContextListener;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.net.Uri;
@@ -136,9 +136,9 @@
void setBiometicContextListener(in IBiometricContextListener listener);
/**
- * Sets an instance of IUdfpsHbmListener for UdfpsController.
+ * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController.
*/
- void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+ void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
/**
* Show a warning that the device is about to go to sleep due to user inactivity.
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 8b898f0..54221ce 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -21,7 +21,6 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
import com.android.internal.view.AppearanceRegion;
@@ -40,7 +39,7 @@
public final IBinder mImeToken;
public final boolean mNavbarColorManagedByIme;
public final int mBehavior;
- public final InsetsVisibilities mRequestedVisibilities;
+ public final int mRequestedVisibleTypes;
public final String mPackageName;
public final int[] mTransientBarTypes;
public final LetterboxDetails[] mLetterboxDetails;
@@ -48,7 +47,7 @@
public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
- boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities,
+ boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
String packageName, @NonNull int[] transientBarTypes,
LetterboxDetails[] letterboxDetails) {
mIcons = new ArrayMap<>(icons);
@@ -62,7 +61,7 @@
mImeToken = imeToken;
mNavbarColorManagedByIme = navbarColorManagedByIme;
mBehavior = behavior;
- mRequestedVisibilities = requestedVisibilities;
+ mRequestedVisibleTypes = requestedVisibleTypes;
mPackageName = packageName;
mTransientBarTypes = transientBarTypes;
mLetterboxDetails = letterboxDetails;
@@ -86,7 +85,7 @@
dest.writeStrongBinder(mImeToken);
dest.writeBoolean(mNavbarColorManagedByIme);
dest.writeInt(mBehavior);
- dest.writeTypedObject(mRequestedVisibilities, 0);
+ dest.writeInt(mRequestedVisibleTypes);
dest.writeString(mPackageName);
dest.writeIntArray(mTransientBarTypes);
dest.writeParcelableArray(mLetterboxDetails, flags);
@@ -112,8 +111,7 @@
final IBinder imeToken = source.readStrongBinder();
final boolean navbarColorManagedByIme = source.readBoolean();
final int behavior = source.readInt();
- final InsetsVisibilities requestedVisibilities =
- source.readTypedObject(InsetsVisibilities.CREATOR);
+ final int requestedVisibleTypes = source.readInt();
final String packageName = source.readString();
final int[] transientBarTypes = source.createIntArray();
final LetterboxDetails[] letterboxDetails =
@@ -121,7 +119,7 @@
return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
- requestedVisibilities, packageName, transientBarTypes,
+ requestedVisibleTypes, packageName, transientBarTypes,
letterboxDetails);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index f7bb16e..40c6a05 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -81,8 +81,7 @@
@EnforcePermission("WRITE_SECURE_SETTINGS")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
- void showInputMethodPickerFromSystem(in IInputMethodClient client,
- int auxiliarySubtypeMode, int displayId);
+ void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
@EnforcePermission("TEST_INPUT_METHOD")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index c50abb3..cc3d906 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -34,6 +34,8 @@
"-Wno-error=deprecated-declarations",
"-Wunused",
"-Wunreachable-code",
+
+ "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
],
cppflags: ["-Wno-conversion-null"],
@@ -212,6 +214,7 @@
"android_content_res_ResourceTimer.cpp",
"android_security_Scrypt.cpp",
"com_android_internal_content_om_OverlayConfig.cpp",
+ "com_android_internal_expresslog_Counter.cpp",
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
@@ -247,6 +250,7 @@
"libscrypt_static",
"libstatssocket_lazy",
"libskia",
+ "libtextclassifier_hash_static",
],
shared_libs: [
@@ -329,6 +333,7 @@
header_libs: [
"bionic_libc_platform_headers",
"dnsproxyd_protocol_headers",
+ "libtextclassifier_hash_headers",
],
},
host: {
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9da28a3..d9ca16e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -194,6 +194,7 @@
extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
+extern int register_com_android_internal_expresslog_Counter(JNIEnv* env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1575,6 +1576,7 @@
REG_JNI(register_android_os_SharedMemory),
REG_JNI(register_android_os_incremental_IncrementalManager),
REG_JNI(register_com_android_internal_content_om_OverlayConfig),
+ REG_JNI(register_com_android_internal_expresslog_Counter),
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 2cd9f89..6762e45 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -22,6 +22,7 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <linux/fs.h>
#include <nativehelper/ScopedUtfChars.h>
#include <stdlib.h>
#include <string.h>
@@ -250,6 +251,16 @@
return INSTALL_FAILED_CONTAINER_ERROR;
}
+ // If a filesystem like f2fs supports per-file compression, set the compression bit before data
+ // writes
+ unsigned int flags;
+ if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
+ ALOGE("Failed to call FS_IOC_GETFLAGS on %s: %s\n", localTmpFileName, strerror(errno));
+ } else if ((flags & FS_COMPR_FL) == 0) {
+ flags |= FS_COMPR_FL;
+ ioctl(fd, FS_IOC_SETFLAGS, &flags);
+ }
+
if (!zipFile->uncompressEntry(zipEntry, fd)) {
ALOGE("Failed uncompressing %s to %s\n", fileName, localTmpFileName);
close(fd);
diff --git a/core/jni/com_android_internal_expresslog_Counter.cpp b/core/jni/com_android_internal_expresslog_Counter.cpp
new file mode 100644
index 0000000..d4a8c23
--- /dev/null
+++ b/core/jni/com_android_internal_expresslog_Counter.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include <utils/hash/farmhash.h>
+
+#include "core_jni_helpers.h"
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+static jclass g_stringClass = nullptr;
+
+/**
+ * Class: com_android_internal_expresslog_Counter
+ * Method: hashString
+ * Signature: (Ljava/lang/String;)J
+ */
+static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
+ ScopedUtfChars name(env, metricNameObj);
+ if (name.c_str() == nullptr) {
+ return 0;
+ }
+
+ return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
+}
+
+static const JNINativeMethod g_methods[] = {
+ {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
+};
+
+static const char* const kCounterPathName = "com/android/internal/expresslog/Counter";
+
+namespace android {
+
+int register_com_android_internal_expresslog_Counter(JNIEnv* env) {
+ jclass stringClass = FindClassOrDie(env, "java/lang/String");
+ g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+
+ return RegisterMethodsOrDie(env, kCounterPathName, g_methods, NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 16e0a59..f0fbff1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7211,6 +7211,10 @@
</intent-filter>
</service>
+ <service android:name="com.android.server.art.BackgroundDexOptJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<provider
android:name="com.android.server.textclassifier.IconsContentProvider"
android:authorities="com.android.textclassifier.icons"
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 88bf18c..eaadc20 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -54,6 +54,12 @@
com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity
</string>
+ <!-- Component name of the activity used to inform a user about a sensor privacy update from
+ SW/HW privacy switches. -->
+ <string name="config_sensorStateChangedActivity" translatable="false">
+ com.android.systemui/com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity
+ </string>
+
<!-- Component name of the activity that shows the request for access to a usb device. -->
<string name="config_usbPermissionActivity" translatable="false">
com.android.systemui/com.android.systemui.usb.tv.TvUsbPermissionActivity
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a1f46fc..3d245e9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2027,10 +2027,6 @@
STREAM_MUSIC as if it's on TV platform. -->
<bool name="config_single_volume">false</bool>
- <!-- Flag indicating whether notification and ringtone volumes
- are controlled together (aliasing is true) or not. -->
- <bool name="config_alias_ring_notif_stream_types">true</bool>
-
<!-- The number of volume steps for the notification stream -->
<integer name="config_audio_notif_vol_steps">7</integer>
@@ -2456,8 +2452,10 @@
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
<bool name="config_dismissDreamOnActivityStart">false</bool>
- <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. -->
- <string name="config_loggable_dream_prefix" translatable="false"></string>
+ <!-- The prefixes of dream component names that are loggable.
+ Matched against ComponentName#flattenToString() for dream components.
+ If empty, logs "other" for all. -->
+ <string-array name="config_loggable_dream_prefixes"></string-array>
<!-- ComponentName of a dream to show whenever the system would otherwise have
gone to sleep. When the PowerManager is asked to go to sleep, it will instead
@@ -2942,6 +2940,9 @@
<string name="config_usbResolverActivity" translatable="false"
>com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
+ <!-- Component name of the activity used to inform a user about a sensor privacy state chage. -->
+ <string name="config_sensorStateChangedActivity" translatable="false"></string>
+
<!-- Component name of the activity used to inform a user about a sensory being blocked because
of privacy settings. -->
<string name="config_sensorUseStartedActivity" translatable="false"
@@ -4945,6 +4946,11 @@
<!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
<bool name="config_faceAuthDismissesKeyguard">true</bool>
+ <!-- Default value for whether a SFPS device is required to be
+ {@link KeyguardUpdateMonitor#isDeviceInteractive()} for fingerprint auth
+ to unlock the device. -->
+ <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 476d36d..f2bbbc4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -282,7 +282,6 @@
<java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/>
<java-symbol type="bool" name="action_bar_embed_tabs" />
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
- <java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
<java-symbol type="integer" name="config_audio_notif_vol_default" />
<java-symbol type="integer" name="config_audio_notif_vol_steps" />
<java-symbol type="integer" name="config_audio_ring_vol_default" />
@@ -374,6 +373,7 @@
<java-symbol type="string" name="config_usbResolverActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity_hwToggle" />
+ <java-symbol type="string" name="config_sensorStateChangedActivity" />
<java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
@@ -2246,7 +2246,7 @@
<java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
<java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
<java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
- <java-symbol type="string" name="config_loggable_dream_prefix" />
+ <java-symbol type="array" name="config_loggable_dream_prefixes" />
<java-symbol type="string" name="config_dozeComponent" />
<java-symbol type="string" name="enable_explore_by_touch_warning_title" />
<java-symbol type="string" name="enable_explore_by_touch_warning_message" />
@@ -2725,6 +2725,7 @@
<java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
<java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
<java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
+ <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
<!-- Face config -->
<java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
new file mode 100644
index 0000000..2711100
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.verification.VerificationWithTimeout;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class ProgramListTest {
+
+ private static final int CREATOR_ARRAY_SIZE = 3;
+ private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 500);
+
+ private static final boolean IS_PURGE = false;
+ private static final boolean IS_COMPLETE = true;
+
+ private static final boolean INCLUDE_CATEGORIES = true;
+ private static final boolean EXCLUDE_MODIFICATIONS = false;
+
+ private static final ProgramSelector.Identifier FM_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ /* value= */ 94300);
+ private static final ProgramSelector.Identifier RDS_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
+ private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+ /* value= */ 0x10000111);
+ private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+ /* value= */ 0x1013);
+ private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo(
+ createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER));
+ private static final RadioManager.ProgramInfo RDS_PROGRAM_INFO = createFmProgramInfo(
+ createProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, RDS_IDENTIFIER));
+
+ private static final Set<Integer> FILTER_IDENTIFIER_TYPES = Set.of(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI);
+ private static final Set<ProgramSelector.Identifier> FILTER_IDENTIFIERS = Set.of(FM_IDENTIFIER);
+
+ private static final ProgramList.Chunk FM_RDS_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
+ IS_COMPLETE, Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+ Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+ private static final ProgramList.Chunk FM_ADD_INCOMPLETE_CHUNK = new ProgramList.Chunk(IS_PURGE,
+ /* complete= */ false, Set.of(FM_PROGRAM_INFO), new ArraySet<>());
+ private static final ProgramList.Filter TEST_FILTER = new ProgramList.Filter(
+ FILTER_IDENTIFIER_TYPES, FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+ private static final Map<String, String> VENDOR_FILTER = Map.of("testVendorKey1",
+ "testVendorValue1", "testVendorKey2", "testVendorValue2");
+
+ private final Executor mExecutor = new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ };
+
+ private RadioTuner mRadioTuner;
+ private ITunerCallback mTunerCallback;
+ private ProgramList mProgramList;
+
+ private ProgramList.ListCallback[] mListCallbackMocks;
+ private ProgramList.OnCompleteListener[] mOnCompleteListenerMocks;
+ @Mock
+ private IRadioService mRadioServiceMock;
+ @Mock
+ private Context mContextMock;
+ @Mock
+ private ITuner mTunerMock;
+ @Mock
+ private RadioTuner.Callback mTunerCallbackMock;
+
+ @Test
+ public void getIdentifierTypes_forFilter() {
+ ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+ FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+ assertWithMessage("Filtered identifier types").that(filter.getIdentifierTypes())
+ .containsExactlyElementsIn(FILTER_IDENTIFIER_TYPES);
+ }
+
+ @Test
+ public void getIdentifiers_forFilter() {
+ ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+ FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+ assertWithMessage("Filtered identifiers").that(filter.getIdentifiers())
+ .containsExactlyElementsIn(FILTER_IDENTIFIERS);
+ }
+
+ @Test
+ public void areCategoriesIncluded_forFilter() {
+ ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+ FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+ assertWithMessage("Filter including categories")
+ .that(filter.areCategoriesIncluded()).isEqualTo(INCLUDE_CATEGORIES);
+ }
+
+ @Test
+ public void areModificationsExcluded_forFilter() {
+ ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+ FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+ assertWithMessage("Filter excluding modifications")
+ .that(filter.areModificationsExcluded()).isEqualTo(EXCLUDE_MODIFICATIONS);
+ }
+
+ @Test
+ public void getVendorFilter_forFilterWithoutVendorFilter_returnsNull() {
+ ProgramList.Filter filter = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+ FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+ assertWithMessage("Filter vendor obtained from filter without vendor filter")
+ .that(filter.getVendorFilter()).isNull();
+ }
+
+ @Test
+ public void getVendorFilter_forFilterWithVendorFilter() {
+ ProgramList.Filter vendorFilter = new ProgramList.Filter(VENDOR_FILTER);
+
+ assertWithMessage("Filter vendor obtained from filter with vendor filter")
+ .that(vendorFilter.getVendorFilter()).isEqualTo(VENDOR_FILTER);
+ }
+
+ @Test
+ public void describeContents_forFilter() {
+ assertWithMessage("Filter contents").that(TEST_FILTER.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void hashCode_withTheSameFilters_equals() {
+ ProgramList.Filter filterCompared = new ProgramList.Filter(FILTER_IDENTIFIER_TYPES,
+ FILTER_IDENTIFIERS, INCLUDE_CATEGORIES, EXCLUDE_MODIFICATIONS);
+
+ assertWithMessage("Hash code of the same filter")
+ .that(filterCompared.hashCode()).isEqualTo(TEST_FILTER.hashCode());
+ }
+
+ @Test
+ public void hashCode_withDifferentFilters_notEquals() {
+ ProgramList.Filter filterCompared = new ProgramList.Filter();
+
+ assertWithMessage("Hash code of the different filter")
+ .that(filterCompared.hashCode()).isNotEqualTo(TEST_FILTER.hashCode());
+ }
+
+ @Test
+ public void writeToParcel_forFilter() {
+ Parcel parcel = Parcel.obtain();
+
+ TEST_FILTER.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ ProgramList.Filter filterFromParcel =
+ ProgramList.Filter.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Filter created from parcel")
+ .that(filterFromParcel).isEqualTo(TEST_FILTER);
+ }
+
+ @Test
+ public void newArray_forFilterCreator() {
+ ProgramList.Filter[] filters = ProgramList.Filter.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Program filters").that(filters).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void isPurge_forChunk() {
+ ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+ Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+ Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+ assertWithMessage("Puring chunk").that(chunk.isPurge()).isEqualTo(IS_PURGE);
+ }
+
+ @Test
+ public void isComplete_forChunk() {
+ ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+ Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+ Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+ assertWithMessage("Complete chunk").that(chunk.isComplete()).isEqualTo(IS_COMPLETE);
+ }
+
+ @Test
+ public void getModified_forChunk() {
+ ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+ Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+ Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+ assertWithMessage("Modified program info in chunk")
+ .that(chunk.getModified()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
+ }
+
+ @Test
+ public void getRemoved_forChunk() {
+ ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
+ Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
+ Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+
+ assertWithMessage("Removed program identifiers in chunk").that(chunk.getRemoved())
+ .containsExactly(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
+ }
+
+ @Test
+ public void describeContents_forChunk() {
+ assertWithMessage("Chunk contents").that(FM_RDS_ADD_CHUNK.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void writeToParcel_forChunk() {
+ Parcel parcel = Parcel.obtain();
+
+ FM_RDS_ADD_CHUNK.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ ProgramList.Chunk chunkFromParcel =
+ ProgramList.Chunk.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Chunk created from parcel")
+ .that(chunkFromParcel).isEqualTo(FM_RDS_ADD_CHUNK);
+ }
+
+ @Test
+ public void newArray_forChunkCreator() {
+ ProgramList.Chunk[] chunks = ProgramList.Chunk.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+ assertWithMessage("Chunks").that(chunks).hasLength(CREATOR_ARRAY_SIZE);
+ }
+
+ @Test
+ public void getDynamicProgramList_forTunerAdapter() throws Exception {
+ createRadioTuner();
+
+ mRadioTuner.getDynamicProgramList(TEST_FILTER);
+
+ verify(mTunerMock).startProgramListUpdates(TEST_FILTER);
+ }
+
+ @Test
+ public void getDynamicProgramList_forTunerAdapterWithServiceDied_throwsException()
+ throws Exception {
+ createRadioTuner();
+ doThrow(new RemoteException()).when(mTunerMock).startProgramListUpdates(any());
+
+ RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+ mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ });
+
+ assertWithMessage("Exception for radio HAL client service died")
+ .that(thrown).hasMessageThat().contains("Service died");
+ }
+
+ @Test
+ public void onProgramListUpdated_withNewIdsAdded_invokesMockedCallbacks() throws Exception {
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ registerListCallbacks(/* numCallbacks= */ 1);
+ addOnCompleteListeners(/* numListeners= */ 1);
+
+ mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemChanged(RDS_IDENTIFIER);
+ verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT).onComplete();
+ assertWithMessage("Program info in program list after adding FM and RDS info")
+ .that(mProgramList.toList()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
+ }
+
+ @Test
+ public void onProgramListUpdated_withIdsRemoved_invokesMockedCallbacks() throws Exception {
+ ProgramList.Chunk fmRemovedChunk = new ProgramList.Chunk(/* purge= */ false,
+ /* complete= */ false, new ArraySet<>(), Set.of(FM_IDENTIFIER));
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ registerListCallbacks(/* numCallbacks= */ 1);
+ mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+ mTunerCallback.onProgramListUpdated(fmRemovedChunk);
+
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
+ assertWithMessage("Program info in program list after removing FM id")
+ .that(mProgramList.toList()).containsExactly(RDS_PROGRAM_INFO);
+ assertWithMessage("Program info FM identifier")
+ .that(mProgramList.get(RDS_IDENTIFIER)).isEqualTo(RDS_PROGRAM_INFO);
+ }
+
+ @Test
+ public void onProgramListUpdated_withIncompleteChunk_notInvokesOnCompleteListener()
+ throws Exception {
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ addOnCompleteListeners(/* numListeners= */ 1);
+
+ mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+ verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT.times(0)).onComplete();
+ }
+
+ @Test
+ public void onProgramListUpdated_withPurgeChunk() throws Exception {
+ ProgramList.Chunk purgeChunk = new ProgramList.Chunk(/* purge= */ true,
+ /* complete= */ true, new ArraySet<>(), new ArraySet<>());
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ registerListCallbacks(/* numCallbacks= */ 1);
+ mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+ mTunerCallback.onProgramListUpdated(purgeChunk);
+
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(FM_IDENTIFIER);
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT).onItemRemoved(RDS_IDENTIFIER);
+ assertWithMessage("Program list after purge chunk applied")
+ .that(mProgramList.toList()).isEmpty();
+ }
+
+ @Test
+ public void registerListCallback_withExecutor() throws Exception {
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ ProgramList.ListCallback listCallbackMock = mock(ProgramList.ListCallback.class);
+
+ mProgramList.registerListCallback(mExecutor, listCallbackMock);
+ mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+ verify(listCallbackMock, CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+ }
+
+ @Test
+ public void onProgramListUpdated_withMultipleListCallBacks() throws Exception {
+ int numCallbacks = 3;
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ registerListCallbacks(numCallbacks);
+
+ mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+ for (int index = 0; index < numCallbacks; index++) {
+ verify(mListCallbackMocks[index], CALLBACK_TIMEOUT).onItemChanged(FM_IDENTIFIER);
+ }
+ }
+
+ @Test
+ public void unregisterListCallback_withProgramUpdated_notInvokesCallback() throws Exception {
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ registerListCallbacks(/* numCallbacks= */ 1);
+
+ mProgramList.unregisterListCallback(mListCallbackMocks[0]);
+ mTunerCallback.onProgramListUpdated(FM_ADD_INCOMPLETE_CHUNK);
+
+ verify(mListCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onItemChanged(any());
+ }
+
+ @Test
+ public void addOnCompleteListener_withExecutor() throws Exception {
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ ProgramList.OnCompleteListener onCompleteListenerMock =
+ mock(ProgramList.OnCompleteListener.class);
+
+ mProgramList.addOnCompleteListener(mExecutor, onCompleteListenerMock);
+ mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+ verify(onCompleteListenerMock, CALLBACK_TIMEOUT).onComplete();
+ }
+
+ @Test
+ public void onProgramListUpdated_withMultipleOnCompleteListeners() throws Exception {
+ int numListeners = 3;
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ addOnCompleteListeners(numListeners);
+
+ mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+ for (int index = 0; index < numListeners; index++) {
+ verify(mOnCompleteListenerMocks[index], CALLBACK_TIMEOUT).onComplete();
+ }
+ }
+
+ @Test
+ public void removeOnCompleteListener_withProgramUpdated_notInvokesListener() throws Exception {
+ createRadioTuner();
+ mProgramList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+ addOnCompleteListeners(/* numListeners= */ 1);
+
+ mProgramList.removeOnCompleteListener(mOnCompleteListenerMocks[0]);
+ mTunerCallback.onProgramListUpdated(FM_RDS_ADD_CHUNK);
+
+ verify(mOnCompleteListenerMocks[0], CALLBACK_TIMEOUT.times(0)).onComplete();
+ }
+
+ @Test
+ public void close_forProgramList_invokesStopProgramListUpdates() throws Exception {
+ createRadioTuner();
+ ProgramList programList = mRadioTuner.getDynamicProgramList(TEST_FILTER);
+
+ programList.close();
+
+ verify(mTunerMock, CALLBACK_TIMEOUT).stopProgramListUpdates();
+ }
+
+ private static ProgramSelector createProgramSelector(int programType,
+ ProgramSelector.Identifier identifier) {
+ return new ProgramSelector(programType, identifier, /* secondaryIds= */ null,
+ /* vendorIds= */ null);
+ }
+
+ private static RadioManager.ProgramInfo createFmProgramInfo(ProgramSelector selector) {
+ return new RadioManager.ProgramInfo(selector, selector.getPrimaryId(),
+ selector.getPrimaryId(), /* relatedContents= */ null, /* infoFlags= */ 0,
+ /* signalQuality= */ 1, new RadioMetadata.Builder().build(),
+ /* vendorInfo= */ null);
+ }
+
+ private void createRadioTuner() throws Exception {
+ RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+ RadioManager.BandConfig band = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
+ /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 200,
+ /* stereo= */ true, /* rds= */ false, /* ta= */ false, /* af= */ false,
+ /* es= */ false));
+
+ doAnswer(invocation -> {
+ mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
+ return mTunerMock;
+ }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+
+ mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
+ /* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
+ }
+
+ private void registerListCallbacks(int numCallbacks) {
+ mListCallbackMocks = new ProgramList.ListCallback[numCallbacks];
+ for (int index = 0; index < numCallbacks; index++) {
+ mListCallbackMocks[index] = mock(ProgramList.ListCallback.class);
+ mProgramList.registerListCallback(mListCallbackMocks[index]);
+ }
+ }
+
+ private void addOnCompleteListeners(int numListeners) {
+ mOnCompleteListenerMocks = new ProgramList.OnCompleteListener[numListeners];
+ for (int index = 0; index < numListeners; index++) {
+ mOnCompleteListenerMocks[index] = mock(ProgramList.OnCompleteListener.class);
+ mProgramList.addOnCompleteListener(mOnCompleteListenerMocks[index]);
+ }
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index f838a5d..365b901 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.radio.Announcement;
import android.hardware.radio.IAnnouncementListener;
@@ -46,6 +47,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Set;
@RunWith(MockitoJUnitRunner.class)
@@ -89,7 +91,8 @@
createAmBandDescriptor();
private static final RadioManager.FmBandConfig FM_BAND_CONFIG = createFmBandConfig();
private static final RadioManager.AmBandConfig AM_BAND_CONFIG = createAmBandConfig();
- private static final RadioManager.ModuleProperties AMFM_PROPERTIES = createAmFmProperties();
+ private static final RadioManager.ModuleProperties AMFM_PROPERTIES =
+ createAmFmProperties(/* dabFrequencyTable= */ null);
/**
* Info flags with live, tuned and stereo enabled
@@ -709,12 +712,20 @@
}
@Test
- public void getDabFrequencyTable_forModuleProperties() {
+ public void getDabFrequencyTable_forModulePropertiesInitializedWithNullTable() {
assertWithMessage("Properties DAB frequency table")
.that(AMFM_PROPERTIES.getDabFrequencyTable()).isNull();
}
@Test
+ public void getDabFrequencyTable_forModulePropertiesInitializedWithEmptyTable() {
+ RadioManager.ModuleProperties properties = createAmFmProperties(new ArrayMap<>());
+
+ assertWithMessage("Properties DAB frequency table")
+ .that(properties.getDabFrequencyTable()).isNull();
+ }
+
+ @Test
public void getVendorInfo_forModuleProperties() {
assertWithMessage("Properties vendor info")
.that(AMFM_PROPERTIES.getVendorInfo()).isEmpty();
@@ -734,8 +745,37 @@
}
@Test
+ public void writeToParcel_forModulePropertiesWithNullDabFrequencyTable() {
+ Parcel parcel = Parcel.obtain();
+
+ AMFM_PROPERTIES.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioManager.ModuleProperties modulePropertiesFromParcel =
+ RadioManager.ModuleProperties.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Module properties created from parcel")
+ .that(modulePropertiesFromParcel).isEqualTo(AMFM_PROPERTIES);
+ }
+
+ @Test
+ public void writeToParcel_forModulePropertiesWithNonnullDabFrequencyTable() {
+ Parcel parcel = Parcel.obtain();
+ RadioManager.ModuleProperties propertiesToParcel = createAmFmProperties(
+ Map.of("5A", 174928, "12D", 229072));
+
+ propertiesToParcel.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ RadioManager.ModuleProperties modulePropertiesFromParcel =
+ RadioManager.ModuleProperties.CREATOR.createFromParcel(parcel);
+ assertWithMessage("Module properties created from parcel")
+ .that(modulePropertiesFromParcel).isEqualTo(propertiesToParcel);
+ }
+
+ @Test
public void equals_withSameProperties_returnsTrue() {
- RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
+ RadioManager.ModuleProperties propertiesCompared =
+ createAmFmProperties(/* dabFrequencyTable= */ null);
assertWithMessage("The same module properties")
.that(AMFM_PROPERTIES).isEqualTo(propertiesCompared);
@@ -747,7 +787,7 @@
PROPERTIES_ID + 1, SERVICE_NAME, CLASS_ID, IMPLEMENTOR, PRODUCT, VERSION,
SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES, IS_INITIALIZATION_REQUIRED,
IS_CAPTURE_SUPPORTED, /* bands= */ null, IS_BG_SCAN_SUPPORTED,
- SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, /* dabFrequencyTable= */ null,
+ SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES, Map.of("5A", 174928),
/* vendorInfo= */ null);
assertWithMessage("Module properties of different id")
@@ -756,7 +796,8 @@
@Test
public void hashCode_withSameModuleProperties_equals() {
- RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
+ RadioManager.ModuleProperties propertiesCompared =
+ createAmFmProperties(/* dabFrequencyTable= */ null);
assertWithMessage("Hash code of the same module properties")
.that(propertiesCompared.hashCode()).isEqualTo(AMFM_PROPERTIES.hashCode());
@@ -989,13 +1030,14 @@
verify(mCloseHandleMock).close();
}
- private static RadioManager.ModuleProperties createAmFmProperties() {
+ private static RadioManager.ModuleProperties createAmFmProperties(
+ @Nullable Map<String, Integer> dabFrequencyTable) {
return new RadioManager.ModuleProperties(PROPERTIES_ID, SERVICE_NAME, CLASS_ID,
IMPLEMENTOR, PRODUCT, VERSION, SERIAL, NUM_TUNERS, NUM_AUDIO_SOURCES,
IS_INITIALIZATION_REQUIRED, IS_CAPTURE_SUPPORTED,
new RadioManager.BandDescriptor[]{AM_BAND_DESCRIPTOR, FM_BAND_DESCRIPTOR},
IS_BG_SCAN_SUPPORTED, SUPPORTED_PROGRAM_TYPES, SUPPORTED_IDENTIFIERS_TYPES,
- /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
+ dabFrequencyTable, /* vendorInfo= */ null);
}
private static RadioManager.FmBandDescriptor createFmBandDescriptor() {
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index fe3ab62..bdba6a1 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -280,6 +280,16 @@
}
@Test
+ public void startBackgroundScan_forTunerAdapter() throws Exception {
+ when(mTunerMock.startBackgroundScan()).thenReturn(false);
+
+ boolean scanStatus = mRadioTuner.startBackgroundScan();
+
+ verify(mTunerMock).startBackgroundScan();
+ assertWithMessage("Status for starting background scan").that(scanStatus).isFalse();
+ }
+
+ @Test
public void isAnalogForced_forTunerAdapter() throws Exception {
when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
index 100eb99..b3af895 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
@@ -145,7 +145,8 @@
mIntentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
mIntentFilter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
mIntentFilter.addAction(TetheringManager.ACTION_TETHER_STATE_CHANGED);
- mContext.registerReceiver(mWifiReceiver, mIntentFilter);
+ mContext.registerReceiver(mWifiReceiver, mIntentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
logv("Clear Wifi before we start the test.");
removeConfiguredNetworksAndDisableWifi();
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
index 1a63660..191756a 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/util/ConnectionUtil.java
@@ -99,7 +99,8 @@
// Register a download receiver for ACTION_DOWNLOAD_COMPLETE
mDownloadReceiver = new DownloadReceiver();
mContext.registerReceiver(mDownloadReceiver,
- new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+ new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
// Register a wifi receiver
mWifiReceiver = new WifiReceiver();
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index f4709ff..cb66fc8 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -471,7 +471,7 @@
protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() {
MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver();
mContext.registerReceiver(receiver, new IntentFilter(
- DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+ DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED_UNAUDITED);
return receiver;
}
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
index 7f81339..5ac84f8 100644
--- a/core/tests/coretests/src/android/app/activity/LocalReceiver.java
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -36,7 +36,8 @@
if (BroadcastTest.BROADCAST_FAIL_REGISTER.equals(intent.getAction())) {
resultString = "Successfully registered, but expected it to fail";
try {
- context.registerReceiver(this, new IntentFilter("foo.bar"));
+ context.registerReceiver(this, new IntentFilter("foo.bar"),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
context.unregisterReceiver(this);
} catch (ReceiverCallNotAllowedException e) {
//resultString = "This is the correct behavior but not yet implemented";
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
index c89f37d..3f3d6a3 100644
--- a/core/tests/coretests/src/android/app/activity/ServiceTest.java
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -172,7 +172,7 @@
pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
mContext.unregisterReceiver(this);
}
- }, new IntentFilter(ACTION_SERVICE_STARTED));
+ }, new IntentFilter(ACTION_SERVICE_STARTED), Context.RECEIVER_EXPORTED_UNAUDITED);
serviceTrigger.run();
try {
diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
index 1509ff9..5dbeac2 100644
--- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
+++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
@@ -49,7 +49,8 @@
final IntentFilter mockFilter = new IntentFilter("android.content.tests.TestAction");
try {
for (int i = 0; i < RECEIVER_LIMIT_PER_APP + 1; i++) {
- mContext.registerReceiver(new EmptyReceiver(), mockFilter);
+ mContext.registerReceiver(new EmptyReceiver(), mockFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
fail("No exception thrown when registering "
+ (RECEIVER_LIMIT_PER_APP + 1) + " receivers");
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
new file mode 100644
index 0000000..f111bf6f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidParameterException;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InputMethodSubtypeHandleTest {
+
+ @Test
+ public void testCreateFromRawHandle() {
+ {
+ final InputMethodSubtypeHandle handle =
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+ assertNotNull(handle);
+ assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+ assertEquals("com.android.test/.Ime1", handle.getImeId());
+ assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+ handle.getComponentName());
+ }
+
+ assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(""));
+
+ // The IME ID must use ComponentName#flattenToShortString(), not #flattenToString().
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/com.android.test.Ime1:subtype:1"));
+
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:0001"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:1!"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:1:"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:1:2"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:a"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:subtype:0x01"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "com.android.test/.Ime1:Subtype:a"));
+ assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+ "ime1:subtype:1"));
+ }
+
+ @Test
+ public void testCreateFromInputMethodInfo() {
+ final InputMethodInfo imi = new InputMethodInfo(
+ "com.android.test", "com.android.test.Ime1", "TestIME", null);
+ {
+ final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, null);
+ assertNotNull(handle);
+ assertEquals("com.android.test/.Ime1:subtype:0", handle.toStringHandle());
+ assertEquals("com.android.test/.Ime1", handle.getImeId());
+ assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+ handle.getComponentName());
+ }
+
+ final InputMethodSubtype subtype =
+ new InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(1).build();
+ {
+ final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, subtype);
+ assertNotNull(handle);
+ assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+ assertEquals("com.android.test/.Ime1", handle.getImeId());
+ assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+ handle.getComponentName());
+ }
+
+ assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, null));
+ assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, subtype));
+ }
+
+ @Test
+ public void testEquality() {
+ assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"));
+ assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode(),
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode());
+
+ assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:2"));
+ assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+ InputMethodSubtypeHandle.of("com.android.test/.Ime2:subtype:1"));
+ }
+
+ @Test
+ public void testParcelablility() {
+ final InputMethodSubtypeHandle original =
+ InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+ final InputMethodSubtypeHandle cloned = cloneHandle(original);
+ assertEquals(original, cloned);
+ assertEquals(original.hashCode(), cloned.hashCode());
+ assertEquals(original.getComponentName(), cloned.getComponentName());
+ assertEquals(original.getImeId(), cloned.getImeId());
+ assertEquals(original.toStringHandle(), cloned.toStringHandle());
+ }
+
+ @Test
+ public void testNoUnnecessaryStringInstantiationInToStringHandle() {
+ final String validHandleStr = "com.android.test/.Ime1:subtype:1";
+ // Verify that toStringHandle() returns the same String object if the input is valid for
+ // an efficient memory usage.
+ assertSame(validHandleStr, InputMethodSubtypeHandle.of(validHandleStr).toStringHandle());
+ }
+
+ @NonNull
+ private static InputMethodSubtypeHandle cloneHandle(
+ @NonNull InputMethodSubtypeHandle original) {
+ Parcel parcel = null;
+ try {
+ parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return InputMethodSubtypeHandle.CREATOR.createFromParcel(parcel);
+ } finally {
+ if (parcel != null) {
+ parcel.recycle();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index c53fb23..048c48b 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -25,7 +25,7 @@
import android.os.Parcel;
import android.os.UserHandle;
import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -65,7 +65,7 @@
new Binder() /* imeToken */,
true /* navbarColorManagedByIme */,
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
- new InsetsVisibilities() /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
"test" /* packageName */,
new int[0] /* transientBarTypes */,
new LetterboxDetails[] {letterboxDetails});
@@ -87,7 +87,7 @@
assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
- assertThat(copy.mRequestedVisibilities).isEqualTo(original.mRequestedVisibilities);
+ assertThat(copy.mRequestedVisibleTypes).isEqualTo(original.mRequestedVisibleTypes);
assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
assertThat(copy.mLetterboxDetails).isEqualTo(original.mLetterboxDetails);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ca543f4..85c6beb 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1951,6 +1951,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "-240296576": {
+ "message": "handleAppTransitionReady: displayId=%d appTransition={%s} openingApps=[%s] closingApps=[%s] transit=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"-237664290": {
"message": "Pause the recording session on display %s",
"level": "VERBOSE",
@@ -2053,12 +2059,6 @@
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecorder.java"
},
- "-134793542": {
- "message": "handleAppTransitionReady: displayId=%d appTransition={%s} excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS",
- "at": "com\/android\/server\/wm\/AppTransitionController.java"
- },
"-134091882": {
"message": "Screenshotting Activity %s",
"level": "VERBOSE",
@@ -2599,6 +2599,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "323235828": {
+ "message": "Delaying app transition for recents animation to finish",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/AppTransitionController.java"
+ },
"327461496": {
"message": "Complete pause: %s",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1d513e4..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1873,6 +1873,11 @@
@Override
public void onActivityPreCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken =
@@ -1904,6 +1909,11 @@
@Override
public void onActivityPostCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
@@ -1921,6 +1931,11 @@
@Override
public void onActivityConfigurationChanged(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
final TransactionRecord transactionRecord = mTransactionManager
.startNewTransaction();
@@ -1934,6 +1949,11 @@
@Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1969,7 +1989,11 @@
if (who instanceof Activity) {
// We will check if the new activity should be split with the activity that launched
// it.
- launchingActivity = (Activity) who;
+ final Activity activity = (Activity) who;
+ // For Activity that is child of another Activity (ActivityGroup), treat the parent
+ // Activity as the launching one because it's window will just be a child of the
+ // parent Activity window.
+ launchingActivity = activity.isChild() ? activity.getParent() : activity;
if (isInPictureInPicture(launchingActivity)) {
// We don't embed activity when it is in PIP.
return super.onStartActivity(who, intent, options);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 7960323..362f1fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -227,7 +227,7 @@
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
- if (curSecondaryContainer != null
+ if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
&& (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
// Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
// the primary TaskFragment.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index dc5b9a1e..64220c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -38,6 +38,7 @@
import android.provider.Settings.Global;
import android.util.Log;
import android.util.SparseArray;
+import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowFocusObserver;
import android.view.InputDevice;
@@ -187,6 +188,31 @@
}
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
this::createExternalInterface, this);
+
+ initBackAnimationRunners();
+ }
+
+ private void initBackAnimationRunners() {
+ final IOnBackInvokedCallback dummyCallback = new IOnBackInvokedCallback.Default();
+ final IRemoteAnimationRunner dummyRunner = new IRemoteAnimationRunner.Default() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ // Animation missing. Simply finish animation.
+ finishedCallback.onAnimationFinished();
+ }
+ };
+
+ final BackAnimationRunner dummyBackRunner =
+ new BackAnimationRunner(dummyCallback, dummyRunner);
+ final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
+ new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
+ // TODO (238474994): register cross activity animation when it's completed.
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, dummyBackRunner);
+ // TODO (236760237): register dialog close animation when it's completed.
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_DIALOG_CLOSE, dummyBackRunner);
}
private void setupAnimationDeveloperSettingsObserver(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
new file mode 100644
index 0000000..2074b6a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Controls the animation of swiping back and returning to another task.
+ *
+ * This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows.
+ * Once the gesture is committed, the second part remains the closing window in place.
+ * The entering window plays the rest of app opening transition to enter full screen.
+ *
+ * This animation is used only for apps that enable back dispatching via
+ * {@link android.window.OnBackInvokedDispatcher}. The controller registers
+ * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
+ * navigation to launcher starts.
+ */
+@ShellMainThread
+class CrossTaskBackAnimation {
+ private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+
+ /**
+ * Minimum scale of the entering window.
+ */
+ private static final float ENTERING_MIN_WINDOW_SCALE = 0.85f;
+
+ /**
+ * Minimum scale of the closing window.
+ */
+ private static final float CLOSING_MIN_WINDOW_SCALE = 0.75f;
+
+ /**
+ * Minimum color scale of the closing window.
+ */
+ private static final float CLOSING_MIN_WINDOW_COLOR_SCALE = 0.1f;
+
+ /**
+ * The margin between the entering window and the closing window
+ */
+ private static final int WINDOW_MARGIN = 35;
+
+ /** Max window translation in the Y axis. */
+ private static final int WINDOW_MAX_DELTA_Y = 160;
+
+ private final Rect mStartTaskRect = new Rect();
+ private final float mCornerRadius;
+
+ // The closing window properties.
+ private final RectF mClosingCurrentRect = new RectF();
+
+ // The entering window properties.
+ private final Rect mEnteringStartRect = new Rect();
+ private final RectF mEnteringCurrentRect = new RectF();
+
+ private final PointF mInitialTouchPos = new PointF();
+ private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+
+ private final Matrix mTransformMatrix = new Matrix();
+
+ private final float[] mTmpFloat9 = new float[9];
+ private final float[] mTmpTranslate = {0, 0, 0};
+
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private SurfaceControl mBackgroundSurface;
+ private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ private boolean mBackInProgress = false;
+
+ private boolean mIsRightEdge;
+ private float mProgress = 0;
+ private PointF mTouchPos = new PointF();
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+
+ private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+
+ final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
+ @Override
+ public void onBackStarted(BackEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CrossTaskBackAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.reset();
+ finishAnimation();
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ };
+
+ final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+ };
+
+ CrossTaskBackAnimation(Context context) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ }
+
+ private float getInterpolatedProgress(float backProgress) {
+ return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+
+ // Offset start rectangle to align task bounds.
+ mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+ mStartTaskRect.offsetTo(0, 0);
+
+ // Draw background.
+ mBackgroundSurface = new SurfaceControl.Builder()
+ .setName("Background of Back Navigation")
+ .setColorLayer()
+ .setHidden(false)
+ .build();
+ mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
+ .setLayer(mBackgroundSurface, -1);
+ mTransaction.apply();
+ }
+
+ private void updateGestureBackProgress(float progress, BackEvent event) {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ return;
+ }
+
+ float touchX = event.getTouchX();
+ float touchY = event.getTouchY();
+ float dX = Math.abs(touchX - mInitialTouchPos.x);
+
+ // The 'follow width' is the width of the window if it completely matches
+ // the gesture displacement.
+ final int width = mStartTaskRect.width();
+ final int height = mStartTaskRect.height();
+
+ // The 'progress width' is the width of the window if it strictly linearly interpolates
+ // to minimum scale base on progress.
+ float enteringScale = mapRange(progress, 1, ENTERING_MIN_WINDOW_SCALE);
+ float closingScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_SCALE);
+ float closingColorScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_COLOR_SCALE);
+
+ // The final width is derived from interpolating between the follow with and progress width
+ // using gesture progress.
+ float enteringWidth = enteringScale * width;
+ float closingWidth = closingScale * width;
+ float enteringHeight = (float) height / width * enteringWidth;
+ float closingHeight = (float) height / width * closingWidth;
+
+ float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+ // Base the window movement in the Y axis on the touch movement in the Y axis.
+ float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+ // Move the window along the Y axis.
+ float closingTop = (height - closingHeight) * 0.5f + deltaY;
+ float enteringTop = (height - enteringHeight) * 0.5f + deltaY;
+ // Move the window along the X axis.
+ float right = width - (progress * WINDOW_MARGIN);
+ float left = right - closingWidth;
+
+ mClosingCurrentRect.set(left, closingTop, right, closingTop + closingHeight);
+ mEnteringCurrentRect.set(left - enteringWidth - WINDOW_MARGIN, enteringTop,
+ left - WINDOW_MARGIN, enteringTop + enteringHeight);
+
+ applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
+ applyColorTransform(mClosingTarget.leash, closingColorScale);
+ applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+ mTransaction.apply();
+ }
+
+ private void updatePostCommitClosingAnimation(float progress) {
+ mTransaction.setLayer(mClosingTarget.leash, 0);
+ float alpha = mapRange(progress, 1, 0);
+ mTransaction.setAlpha(mClosingTarget.leash, alpha);
+ }
+
+ private void updatePostCommitEnteringAnimation(float progress) {
+ float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+ float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+ float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+ float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+
+ mEnteringCurrentRect.set(left, top, left + width, top + height);
+ applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+ }
+
+ /** Transform the target window to match the target rect. */
+ private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
+ if (leash == null) {
+ return;
+ }
+
+ final float scale = targetRect.width() / mStartTaskRect.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+ mTransaction.setMatrix(leash, mTransformMatrix, mTmpFloat9)
+ .setWindowCrop(leash, mStartTaskRect)
+ .setCornerRadius(leash, cornerRadius);
+ }
+
+ private void applyColorTransform(SurfaceControl leash, float colorScale) {
+ if (leash == null) {
+ return;
+ }
+ computeScaleTransformMatrix(colorScale, mTmpFloat9);
+ mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
+ }
+
+ static void computeScaleTransformMatrix(float scale, float[] matrix) {
+ matrix[0] = scale;
+ matrix[1] = 0;
+ matrix[2] = 0;
+ matrix[3] = 0;
+ matrix[4] = scale;
+ matrix[5] = 0;
+ matrix[6] = 0;
+ matrix[7] = 0;
+ matrix[8] = scale;
+ }
+
+ private void finishAnimation() {
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+
+ if (mBackgroundSurface != null) {
+ mBackgroundSurface.release();
+ mBackgroundSurface = null;
+ }
+
+ mBackInProgress = false;
+ mTransformMatrix.reset();
+ mClosingCurrentRect.setEmpty();
+ mInitialTouchPos.set(0, 0);
+
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ }
+
+ private void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (!mBackInProgress) {
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
+ mBackInProgress = true;
+ }
+ mProgress = backEvent.getProgress();
+ mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ updateGestureBackProgress(getInterpolatedProgress(mProgress), backEvent);
+ }
+
+ private void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ finishAnimation();
+ return;
+ }
+
+ // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+ // coordinate of the gesture driven phase.
+ mEnteringCurrentRect.round(mEnteringStartRect);
+
+ ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(300);
+ valueAnimator.setInterpolator(mInterpolator);
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = animation.getAnimatedFraction();
+ updatePostCommitEnteringAnimation(progress);
+ updatePostCommitClosingAnimation(progress);
+ mTransaction.apply();
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3972b59..d244295 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -24,6 +24,7 @@
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
@@ -37,7 +38,6 @@
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
-import static com.android.wm.shell.floating.FloatingTasksController.SHOW_FLOATING_TASKS_AS_BUBBLES;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -150,6 +150,9 @@
private final ShellExecutor mBackgroundExecutor;
+ // Whether or not we should show bubbles pinned at the bottom of the screen.
+ private boolean mIsBubbleBarEnabled;
+
private BubbleLogger mLogger;
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@@ -210,7 +213,6 @@
/** Drag and drop controller to register listener for onDragStarted. */
private DragAndDropController mDragAndDropController;
-
public BubbleController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
@@ -526,6 +528,12 @@
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
+ // TODO(b/256873975): Should pass this into the constructor once flags are available to shell.
+ /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */
+ public void setBubbleBarEnabled(boolean enabled) {
+ mIsBubbleBarEnabled = enabled;
+ }
+
/** Whether this userId belongs to the current user. */
private boolean isCurrentProfile(int userId) {
return userId == UserHandle.USER_ALL
@@ -591,7 +599,8 @@
}
mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
- if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) {
+
+ if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) {
mBubblePositioner.setUsePinnedLocation(true);
} else {
mBubblePositioner.setUsePinnedLocation(false);
@@ -700,7 +709,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- mContext.registerReceiver(mBroadcastReceiver, filter);
+ mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -967,14 +976,18 @@
}
/**
- * Adds a bubble for a specific intent. These bubbles are <b>not</b> backed by a notification
- * and remain until the user dismisses the bubble or bubble stack. Only one intent bubble
- * is supported at a time.
+ * Adds and expands bubble for a specific intent. These bubbles are <b>not</b> backed by a n
+ * otification and remain until the user dismisses the bubble or bubble stack. Only one intent
+ * bubble is supported at a time.
*
* @param intent the intent to display in the bubble expanded view.
*/
- public void addAppBubble(Intent intent) {
+ public void showAppBubble(Intent intent) {
if (intent == null || intent.getPackage() == null) return;
+
+ PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
+ if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
+
Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
@@ -1497,18 +1510,23 @@
}
PackageManager packageManager = getPackageManagerForUser(
context, entry.getStatusBarNotification().getUser().getIdentifier());
- ActivityInfo info =
- intent.getIntent().resolveActivityInfo(packageManager, 0);
+ return isResizableActivity(intent.getIntent(), packageManager, entry.getKey());
+ }
+
+ static boolean isResizableActivity(Intent intent, PackageManager packageManager, String key) {
+ if (intent == null) {
+ Log.w(TAG, "Unable to send as bubble: " + key + " null intent");
+ return false;
+ }
+ ActivityInfo info = intent.resolveActivityInfo(packageManager, 0);
if (info == null) {
- Log.w(TAG, "Unable to send as bubble, "
- + entry.getKey() + " couldn't find activity info for intent: "
- + intent);
+ Log.w(TAG, "Unable to send as bubble: " + key
+ + " couldn't find activity info for intent: " + intent);
return false;
}
if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
- Log.w(TAG, "Unable to send as bubble, "
- + entry.getKey() + " activity is not resizable for intent: "
- + intent);
+ Log.w(TAG, "Unable to send as bubble: " + key
+ + " activity is not resizable for intent: " + intent);
return false;
}
return true;
@@ -1682,6 +1700,13 @@
}
@Override
+ public void showAppBubble(Intent intent) {
+ mMainExecutor.execute(() -> {
+ BubbleController.this.showAppBubble(intent);
+ });
+ }
+
+ @Override
public boolean handleDismissalInterception(BubbleEntry entry,
@Nullable List<BubbleEntry> children, IntConsumer removeCallback,
Executor callbackExecutor) {
@@ -1792,6 +1817,13 @@
}
@Override
+ public void setBubbleBarEnabled(boolean enabled) {
+ mMainExecutor.execute(() -> {
+ BubbleController.this.setBubbleBarEnabled(enabled);
+ });
+ }
+
+ @Override
public void onNotificationPanelExpandedChanged(boolean expanded) {
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 7f891ec..465d1ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -22,6 +22,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.app.NotificationChannel;
+import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
@@ -108,6 +109,15 @@
void expandStackAndSelectBubble(Bubble bubble);
/**
+ * Adds and expands bubble that is not notification based, but instead based on an intent from
+ * the app. The intent must be explicit (i.e. include a package name or fully qualified
+ * component class name) and the activity for it should be resizable.
+ *
+ * @param intent the intent to populate the bubble.
+ */
+ void showAppBubble(Intent intent);
+
+ /**
* @return a bubble that matches the provided shortcutId, if one exists.
*/
@Nullable
@@ -232,6 +242,11 @@
*/
void onUserRemoved(int removedUserId);
+ /**
+ * Sets whether bubble bar should be enabled or not.
+ */
+ void setBubbleBarEnabled(boolean enabled);
+
/** Listener to find out about stack expansion / collapse events. */
interface BubbleExpandListener {
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8b8e192..1977dcb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -24,7 +24,6 @@
import android.os.Handler;
import android.os.SystemProperties;
import android.view.IWindowManager;
-import android.view.WindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
@@ -65,8 +64,6 @@
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.floating.FloatingTasks;
-import com.android.wm.shell.floating.FloatingTasksController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
@@ -576,47 +573,6 @@
}
//
- // Floating tasks
- //
-
- @WMSingleton
- @Provides
- static Optional<FloatingTasks> provideFloatingTasks(
- Optional<FloatingTasksController> floatingTaskController) {
- return floatingTaskController.map((controller) -> controller.asFloatingTasks());
- }
-
- @WMSingleton
- @Provides
- static Optional<FloatingTasksController> provideFloatingTasksController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- Optional<BubbleController> bubbleController,
- WindowManager windowManager,
- ShellTaskOrganizer organizer,
- TaskViewTransitions taskViewTransitions,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellBackgroundThread ShellExecutor bgExecutor,
- SyncTransactionQueue syncQueue) {
- if (FloatingTasksController.FLOATING_TASKS_ENABLED) {
- return Optional.of(new FloatingTasksController(context,
- shellInit,
- shellController,
- shellCommandHandler,
- bubbleController,
- windowManager,
- organizer,
- taskViewTransitions,
- mainExecutor,
- bgExecutor,
- syncQueue));
- } else {
- return Optional.empty();
- }
- }
-
- //
// Starting window
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
deleted file mode 100644
index 83a1734..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.floating;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.floating.views.FloatingTaskView;
-
-import java.util.Objects;
-
-/**
- * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved
- * close to the target to be stuck to it unless moved out again.
- */
-public class FloatingDismissController {
-
- /** Velocity required to dismiss the view without dragging it into the dismiss target. */
- private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f;
- /**
- * Max velocity that the view can be moving through the target with to stick (i.e. if it's
- * more than this velocity, it will pass through the target.
- */
- private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f;
- /**
- * Percentage of the target width to use to determine if an object flung towards the target
- * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a
- * 200px-wide area around the target will be considered 'near' enough get dismissed).
- */
- private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f;
- /** Minimum alpha to apply to the view being dismissed when it is in the target. */
- private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f;
- /** Amount to scale down the view being dismissed when it is in the target. */
- private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f;
-
- private Context mContext;
- private FloatingTasksController mController;
- private FloatingTaskLayer mParent;
-
- private DismissView mDismissView;
- private ValueAnimator mDismissAnimator;
- private View mViewBeingDismissed;
- private float mDismissSizePercent;
- private float mDismissSize;
-
- /**
- * The currently magnetized object, which is being dragged and will be attracted to the magnetic
- * dismiss target.
- */
- private MagnetizedObject<View> mMagnetizedObject;
- /**
- * The MagneticTarget instance for our circular dismiss view. This is added to the
- * MagnetizedObject instances for the view being dragged.
- */
- private MagnetizedObject.MagneticTarget mMagneticTarget;
- /** Magnet listener that handles animating and dismissing the view. */
- private MagnetizedObject.MagnetListener mFloatingViewMagnetListener;
-
- public FloatingDismissController(Context context, FloatingTasksController controller,
- FloatingTaskLayer parent) {
- mContext = context;
- mController = controller;
- mParent = parent;
- updateSizes();
- createAndAddDismissView();
-
- mDismissAnimator = ValueAnimator.ofFloat(1f, 0f);
- mDismissAnimator.addUpdateListener(animation -> {
- final float value = (float) animation.getAnimatedValue();
- if (mDismissView != null) {
- mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f);
- mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f);
- final float scaleValue = Math.max(value, mDismissSizePercent);
- mDismissView.getCircle().setScaleX(scaleValue);
- mDismissView.getCircle().setScaleY(scaleValue);
- }
- if (mViewBeingDismissed != null) {
- // TODO: alpha doesn't actually apply to taskView currently.
- mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA));
- mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
- mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT));
- }
- });
-
- mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() {
- @Override
- public void onStuckToTarget(
- @NonNull MagnetizedObject.MagneticTarget target) {
- animateDismissing(/* dismissing= */ true);
- }
-
- @Override
- public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- float velX, float velY, boolean wasFlungOut) {
- animateDismissing(/* dismissing= */ false);
- mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY,
- wasFlungOut);
- }
-
- @Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- doDismiss();
- }
- };
- }
-
- /** Updates all the sizes used and applies them to the {@link DismissView}. */
- public void updateSizes() {
- Resources res = mContext.getResources();
- mDismissSize = res.getDimensionPixelSize(
- R.dimen.floating_task_dismiss_circle_size);
- final float minDismissSize = res.getDimensionPixelSize(
- R.dimen.floating_dismiss_circle_small);
- mDismissSizePercent = minDismissSize / mDismissSize;
-
- if (mDismissView != null) {
- mDismissView.updateResources();
- }
- }
-
- /** Prepares the view being dragged to be magnetic. */
- public void setUpMagneticObject(View viewBeingDragged) {
- mViewBeingDismissed = viewBeingDragged;
- mMagnetizedObject = getMagnetizedView(viewBeingDragged);
- mMagnetizedObject.clearAllTargets();
- mMagnetizedObject.addTarget(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener);
- }
-
- /** Shows or hides the dismiss target. */
- public void showDismiss(boolean show) {
- if (show) {
- mDismissView.show();
- } else {
- mDismissView.hide();
- }
- }
-
- /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
- public boolean passEventToMagnetizedObject(MotionEvent event) {
- return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
- }
-
- private void createAndAddDismissView() {
- if (mDismissView != null) {
- mParent.removeView(mDismissView);
- }
- mDismissView = new DismissView(mContext);
- mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size);
- mDismissView.updateResources();
- mParent.addView(mDismissView);
-
- final float dismissRadius = mDismissSize;
- // Save the MagneticTarget instance for the newly set up view - we'll add this to the
- // MagnetizedObjects when the dismiss view gets shown.
- mMagneticTarget = new MagnetizedObject.MagneticTarget(
- mDismissView.getCircle(), (int) dismissRadius);
- }
-
- private MagnetizedObject<View> getMagnetizedView(View v) {
- if (mMagnetizedObject != null
- && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) {
- // Same view being dragged, we can reuse the magnetic object.
- return mMagnetizedObject;
- }
- MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>(
- mContext,
- v,
- DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y
- ) {
- @Override
- public float getWidth(@NonNull View underlyingObject) {
- return underlyingObject.getWidth();
- }
-
- @Override
- public float getHeight(@NonNull View underlyingObject) {
- return underlyingObject.getHeight();
- }
-
- @Override
- public void getLocationOnScreen(@NonNull View underlyingObject,
- @NonNull int[] loc) {
- loc[0] = (int) underlyingObject.getTranslationX();
- loc[1] = (int) underlyingObject.getTranslationY();
- }
- };
- magnetizedView.setHapticsEnabled(true);
- magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
- magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY);
- magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT);
- return magnetizedView;
- }
-
- /** Animates the dismiss treatment on the view being dismissed. */
- private void animateDismissing(boolean shouldDismiss) {
- if (mViewBeingDismissed == null) {
- return;
- }
- if (shouldDismiss) {
- mDismissAnimator.removeAllListeners();
- mDismissAnimator.start();
- } else {
- mDismissAnimator.removeAllListeners();
- mDismissAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- resetDismissAnimator();
- }
- });
- mDismissAnimator.reverse();
- }
- }
-
- /** Actually dismisses the view. */
- private void doDismiss() {
- mDismissView.hide();
- mController.removeTask();
- resetDismissAnimator();
- mViewBeingDismissed = null;
- }
-
- private void resetDismissAnimator() {
- mDismissAnimator.removeAllListeners();
- mDismissAnimator.cancel();
- if (mDismissView != null) {
- mDismissView.cancelAnimators();
- mDismissView.getCircle().setScaleX(1f);
- mDismissView.getCircle().setScaleY(1f);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
deleted file mode 100644
index f86d467..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.floating;
-
-import android.content.Intent;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to interact with floating tasks.
- */
-@ExternalThread
-public interface FloatingTasks {
-
- /**
- * Shows, stashes, or un-stashes the floating task depending on state:
- * - If there is no floating task for this intent, it shows the task for the provided intent.
- * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
- * - If there is a floating task for this intent, and it's not stashed, this stashes it.
- */
- void showOrSetStashed(Intent intent);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
deleted file mode 100644
index b3c09d3..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.floating;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.os.SystemProperties;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.bubbles.BubbleController;
-import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellBackgroundThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.floating.views.FloatingTaskView;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-import java.util.Optional;
-
-/**
- * Entry point for creating and managing floating tasks.
- *
- * A single window layer is added and the task(s) are displayed using a {@link FloatingTaskView}
- * within that window.
- *
- * Currently optimized for a single task. Multiple tasks are not supported.
- */
-public class FloatingTasksController implements RemoteCallable<FloatingTasksController>,
- ConfigurationChangeListener {
-
- private static final String TAG = FloatingTasksController.class.getSimpleName();
-
- public static final boolean FLOATING_TASKS_ENABLED =
- SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
- public static final boolean SHOW_FLOATING_TASKS_AS_BUBBLES =
- SystemProperties.getBoolean("persist.wm.debug.floating_tasks_as_bubbles", false);
-
- @VisibleForTesting
- static final int SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET = 600;
-
- // Only used for testing
- private Configuration mConfig;
- private boolean mFloatingTasksEnabledForTests;
-
- private FloatingTaskImpl mImpl = new FloatingTaskImpl();
- private Context mContext;
- private ShellController mShellController;
- private ShellCommandHandler mShellCommandHandler;
- private @Nullable BubbleController mBubbleController;
- private WindowManager mWindowManager;
- private ShellTaskOrganizer mTaskOrganizer;
- private TaskViewTransitions mTaskViewTransitions;
- private @ShellMainThread ShellExecutor mMainExecutor;
- // TODO: mBackgroundThread is not used but we'll probs need it eventually?
- private @ShellBackgroundThread ShellExecutor mBackgroundThread;
- private SyncTransactionQueue mSyncQueue;
-
- private boolean mIsFloatingLayerAdded;
- private FloatingTaskLayer mFloatingTaskLayer;
- private final Point mLastPosition = new Point(-1, -1);
-
- private Task mTask;
-
- // Simple class to hold onto info for intent or shortcut based tasks.
- public static class Task {
- public int taskId = INVALID_TASK_ID;
- @Nullable
- public Intent intent;
- @Nullable
- public ShortcutInfo info;
- @Nullable
- public FloatingTaskView floatingView;
- }
-
- public FloatingTasksController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellCommandHandler shellCommandHandler,
- Optional<BubbleController> bubbleController,
- WindowManager windowManager,
- ShellTaskOrganizer organizer,
- TaskViewTransitions transitions,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellBackgroundThread ShellExecutor bgExceutor,
- SyncTransactionQueue syncTransactionQueue) {
- mContext = context;
- mShellController = shellController;
- mShellCommandHandler = shellCommandHandler;
- mBubbleController = bubbleController.get();
- mWindowManager = windowManager;
- mTaskOrganizer = organizer;
- mTaskViewTransitions = transitions;
- mMainExecutor = mainExecutor;
- mBackgroundThread = bgExceutor;
- mSyncQueue = syncTransactionQueue;
- if (isFloatingTasksEnabled()) {
- shellInit.addInitCallback(this::onInit, this);
- }
- }
-
- protected void onInit() {
- mShellController.addConfigurationChangeListener(this);
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS,
- this::createExternalInterface, this);
- mShellCommandHandler.addDumpCallback(this::dump, this);
- }
-
- /** Only used for testing. */
- @VisibleForTesting
- void setConfig(Configuration config) {
- mConfig = config;
- }
-
- /** Only used for testing. */
- @VisibleForTesting
- void setFloatingTasksEnabled(boolean enabled) {
- mFloatingTasksEnabledForTests = enabled;
- }
-
- /** Whether the floating layer is available. */
- boolean isFloatingLayerAvailable() {
- Configuration config = mConfig == null
- ? mContext.getResources().getConfiguration()
- : mConfig;
- return config.smallestScreenWidthDp >= SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
- }
-
- /** Whether floating tasks are enabled. */
- boolean isFloatingTasksEnabled() {
- return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests;
- }
-
- private ExternalInterfaceBinder createExternalInterface() {
- return new IFloatingTasksImpl(this);
- }
-
- @Override
- public void onThemeChanged() {
- if (mIsFloatingLayerAdded) {
- mFloatingTaskLayer.updateSizes();
- }
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- // TODO: probably other stuff here to do (e.g. handle rotation)
- if (mIsFloatingLayerAdded) {
- mFloatingTaskLayer.updateSizes();
- }
- }
-
- /** Returns false if the task shouldn't be shown. */
- private boolean canShowTask(Intent intent) {
- ProtoLog.d(WM_SHELL_FLOATING_APPS, "canShowTask -- %s", intent);
- if (!isFloatingTasksEnabled() || !isFloatingLayerAvailable()) return false;
- if (intent == null) {
- ProtoLog.e(WM_SHELL_FLOATING_APPS, "canShowTask given null intent, doing nothing");
- return false;
- }
- return true;
- }
-
- /** Returns true if the task was or should be shown as a bubble. */
- private boolean maybeShowTaskAsBubble(Intent intent) {
- if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubbleController != null) {
- removeFloatingLayer();
- if (intent.getPackage() != null) {
- mBubbleController.addAppBubble(intent);
- ProtoLog.d(WM_SHELL_FLOATING_APPS, "showing floating task as bubble: %s", intent);
- } else {
- ProtoLog.d(WM_SHELL_FLOATING_APPS,
- "failed to show floating task as bubble: %s; unknown package", intent);
- }
- return true;
- }
- return false;
- }
-
- /**
- * Shows, stashes, or un-stashes the floating task depending on state:
- * - If there is no floating task for this intent, it shows this the provided task.
- * - If there is a floating task for this intent, but it's stashed, this un-stashes it.
- * - If there is a floating task for this intent, and it's not stashed, this stashes it.
- */
- public void showOrSetStashed(Intent intent) {
- if (!canShowTask(intent)) return;
- if (maybeShowTaskAsBubble(intent)) return;
-
- addFloatingLayer();
-
- if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
- // The task is already added, toggle the stash state.
- mFloatingTaskLayer.setStashed(mTask, !mTask.floatingView.isStashed());
- return;
- }
-
- // If we're here it's either a new or different task
- showNewTask(intent);
- }
-
- /**
- * Shows a floating task with the provided intent.
- * If the same task is present it will un-stash it or do nothing if it is already un-stashed.
- * Removes any other floating tasks that might exist.
- */
- public void showTask(Intent intent) {
- if (!canShowTask(intent)) return;
- if (maybeShowTaskAsBubble(intent)) return;
-
- addFloatingLayer();
-
- if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) {
- // The task is already added, show it if it's stashed.
- if (mTask.floatingView.isStashed()) {
- mFloatingTaskLayer.setStashed(mTask, false);
- }
- return;
- }
- showNewTask(intent);
- }
-
- private void showNewTask(Intent intent) {
- if (mTask != null && !intent.filterEquals(mTask.intent)) {
- mFloatingTaskLayer.removeAllTaskViews();
- mTask.floatingView.cleanUpTaskView();
- mTask = null;
- }
-
- FloatingTaskView ftv = new FloatingTaskView(mContext, this);
- ftv.createTaskView(mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
-
- mTask = new Task();
- mTask.floatingView = ftv;
- mTask.intent = intent;
-
- // Add & start the task.
- mFloatingTaskLayer.addTask(mTask);
- ProtoLog.d(WM_SHELL_FLOATING_APPS, "showNewTask, startingIntent: %s", intent);
- mTask.floatingView.startTask(mMainExecutor, mTask);
- }
-
- /**
- * Removes the task and cleans up the view.
- */
- public void removeTask() {
- if (mTask != null) {
- ProtoLog.d(WM_SHELL_FLOATING_APPS, "Removing task with id=%d", mTask.taskId);
-
- if (mTask.floatingView != null) {
- // TODO: animate it
- mFloatingTaskLayer.removeView(mTask.floatingView);
- mTask.floatingView.cleanUpTaskView();
- }
- removeFloatingLayer();
- }
- }
-
- /**
- * Whether there is a floating task and if it is stashed.
- */
- public boolean isStashed() {
- return isTaskAttached(mTask) && mTask.floatingView.isStashed();
- }
-
- /**
- * If a floating task exists, this sets whether it is stashed and animates if needed.
- */
- public void setStashed(boolean shouldStash) {
- if (mTask != null && mTask.floatingView != null && mIsFloatingLayerAdded) {
- mFloatingTaskLayer.setStashed(mTask, shouldStash);
- }
- }
-
- /**
- * Saves the last position the floating task was in so that it can be put there again.
- */
- public void setLastPosition(int x, int y) {
- mLastPosition.set(x, y);
- }
-
- /**
- * Returns the last position the floating task was in.
- */
- public Point getLastPosition() {
- return mLastPosition;
- }
-
- /**
- * Whether the provided task has a view that's attached to the floating layer.
- */
- private boolean isTaskAttached(Task t) {
- return t != null && t.floatingView != null
- && mIsFloatingLayerAdded
- && mFloatingTaskLayer.getTaskViewCount() > 0
- && Objects.equals(mFloatingTaskLayer.getFirstTaskView(), t.floatingView);
- }
-
- // TODO: when this is added, if there are bubbles, they get hidden? Is only one layer of this
- // type allowed? Bubbles & floating tasks should probably be in the same layer to reduce
- // # of windows.
- private void addFloatingLayer() {
- if (mIsFloatingLayerAdded) {
- return;
- }
-
- mFloatingTaskLayer = new FloatingTaskLayer(mContext, this, mWindowManager);
-
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
- PixelFormat.TRANSLUCENT
- );
- params.setTrustedOverlay();
- params.setFitInsetsTypes(0);
- params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
- params.setTitle("FloatingTaskLayer");
- params.packageName = mContext.getPackageName();
- params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- try {
- mIsFloatingLayerAdded = true;
- mWindowManager.addView(mFloatingTaskLayer, params);
- } catch (IllegalStateException e) {
- // This means the floating layer has already been added which shouldn't happen.
- e.printStackTrace();
- }
- }
-
- private void removeFloatingLayer() {
- if (!mIsFloatingLayerAdded) {
- return;
- }
- try {
- mIsFloatingLayerAdded = false;
- if (mFloatingTaskLayer != null) {
- mWindowManager.removeView(mFloatingTaskLayer);
- }
- } catch (IllegalArgumentException e) {
- // This means the floating layer has already been removed which shouldn't happen.
- e.printStackTrace();
- }
- }
-
- /**
- * Description of current floating task state.
- */
- private void dump(PrintWriter pw, String prefix) {
- pw.println("FloatingTaskController state:");
- pw.print(" isFloatingLayerAvailable= "); pw.println(isFloatingLayerAvailable());
- pw.print(" isFloatingTasksEnabled= "); pw.println(isFloatingTasksEnabled());
- pw.print(" mIsFloatingLayerAdded= "); pw.println(mIsFloatingLayerAdded);
- pw.print(" mLastPosition= "); pw.println(mLastPosition);
- pw.println();
- }
-
- /** Returns the {@link FloatingTasks} implementation. */
- public FloatingTasks asFloatingTasks() {
- return mImpl;
- }
-
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- /**
- * The interface for calls from outside the shell, within the host process.
- */
- @ExternalThread
- private class FloatingTaskImpl implements FloatingTasks {
- @Override
- public void showOrSetStashed(Intent intent) {
- mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent));
- }
- }
-
- /**
- * The interface for calls from outside the host process.
- */
- @BinderThread
- private static class IFloatingTasksImpl extends IFloatingTasks.Stub
- implements ExternalInterfaceBinder {
- private FloatingTasksController mController;
-
- IFloatingTasksImpl(FloatingTasksController controller) {
- mController = controller;
- }
-
- /**
- * Invalidates this instance, preventing future calls from updating the controller.
- */
- @Override
- public void invalidate() {
- mController = null;
- }
-
- public void showTask(Intent intent) {
- executeRemoteCallWithTaskPermission(mController, "showTask",
- (controller) -> controller.showTask(intent));
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
deleted file mode 100644
index c922109..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.floating.views;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.wm.shell.R;
-
-/**
- * Displays the menu items for a floating task view (e.g. close).
- */
-public class FloatingMenuView extends LinearLayout {
-
- private int mItemSize;
- private int mItemMargin;
-
- public FloatingMenuView(Context context) {
- super(context);
- setOrientation(LinearLayout.HORIZONTAL);
- setGravity(Gravity.CENTER);
-
- mItemSize = context.getResources().getDimensionPixelSize(
- R.dimen.floating_task_menu_item_size);
- mItemMargin = context.getResources().getDimensionPixelSize(
- R.dimen.floating_task_menu_item_padding);
- }
-
- /** Adds a clickable item to the menu bar. Items are ordered as added. */
- public void addMenuItem(@Nullable Drawable drawable, View.OnClickListener listener) {
- ImageView itemView = new ImageView(getContext());
- itemView.setScaleType(ImageView.ScaleType.CENTER);
- if (drawable != null) {
- itemView.setImageDrawable(drawable);
- }
- LinearLayout.LayoutParams lp = new LayoutParams(mItemSize,
- ViewGroup.LayoutParams.MATCH_PARENT);
- lp.setMarginStart(mItemMargin);
- lp.setMarginEnd(mItemMargin);
- addView(itemView, lp);
-
- itemView.setOnClickListener(listener);
- }
-
- /**
- * The menu extends past the top of the TaskView because of the rounded corners. This means
- * to center content in the menu we must subtract the radius (i.e. the amount of space covered
- * by TaskView).
- */
- public void setCornerRadius(float radius) {
- setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
deleted file mode 100644
index 16dab24..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java
+++ /dev/null
@@ -1,687 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.floating.views;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FlingAnimation;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.R;
-import com.android.wm.shell.floating.FloatingDismissController;
-import com.android.wm.shell.floating.FloatingTasksController;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * This is the layout that {@link FloatingTaskView}s are contained in. It handles input and
- * movement of the task views.
- */
-public class FloatingTaskLayer extends FrameLayout
- implements ViewTreeObserver.OnComputeInternalInsetsListener {
-
- private static final String TAG = FloatingTaskLayer.class.getSimpleName();
-
- /** How big to make the task view based on screen width of the largest size. */
- private static final float START_SIZE_WIDTH_PERCENT = 0.33f;
- /** Min fling velocity required to move the view from one side of the screen to the other. */
- private static final float ESCAPE_VELOCITY = 750f;
- /** Amount of friction to apply to fling animations. */
- private static final float FLING_FRICTION = 1.9f;
-
- private final FloatingTasksController mController;
- private final FloatingDismissController mDismissController;
- private final WindowManager mWindowManager;
- private final TouchHandlerImpl mTouchHandler;
-
- private final Region mTouchableRegion = new Region();
- private final Rect mPositionRect = new Rect();
- private final Point mDefaultStartPosition = new Point();
- private final Point mTaskViewSize = new Point();
- private WindowInsets mWindowInsets;
- private int mVerticalPadding;
- private int mOverhangWhenStashed;
-
- private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
- private ViewTreeObserver.OnDrawListener mSystemGestureExclusionListener =
- this::updateSystemGestureExclusion;
-
- /** Interface allowing something to handle the touch events going to a task. */
- interface FloatingTaskTouchHandler {
- void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float viewInitialX, float viewInitialY);
-
- void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float dx, float dy);
-
- void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float dx, float dy, float velX, float velY);
-
- void onClick(@NonNull FloatingTaskView v);
- }
-
- public FloatingTaskLayer(Context context,
- FloatingTasksController controller,
- WindowManager windowManager) {
- super(context);
- // TODO: Why is this necessary? Without it FloatingTaskView does not render correctly.
- setBackgroundColor(Color.argb(0, 0, 0, 0));
-
- mController = controller;
- mWindowManager = windowManager;
- updateSizes();
-
- // TODO: Might make sense to put dismiss controller in the touch handler since that's the
- // main user of dismiss controller.
- mDismissController = new FloatingDismissController(context, mController, this);
- mTouchHandler = new TouchHandlerImpl();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- getViewTreeObserver().addOnDrawListener(mSystemGestureExclusionListener);
- setOnApplyWindowInsetsListener((view, windowInsets) -> {
- if (!windowInsets.equals(mWindowInsets)) {
- mWindowInsets = windowInsets;
- updateSizes();
- }
- return windowInsets;
- });
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- getViewTreeObserver().removeOnDrawListener(mSystemGestureExclusionListener);
- }
-
- @Override
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- mTouchableRegion.setEmpty();
- getTouchableRegion(mTouchableRegion);
- inoutInfo.touchableRegion.set(mTouchableRegion);
- }
-
- /** Adds a floating task to the layout. */
- public void addTask(FloatingTasksController.Task task) {
- if (task.floatingView == null) return;
-
- task.floatingView.setTouchHandler(mTouchHandler);
- addView(task.floatingView, new LayoutParams(mTaskViewSize.x, mTaskViewSize.y));
- updateTaskViewPosition(task.floatingView);
- }
-
- /** Animates the stashed state of the provided task, if it's part of the floating layer. */
- public void setStashed(FloatingTasksController.Task task, boolean shouldStash) {
- if (task.floatingView != null && task.floatingView.getParent() == this) {
- mTouchHandler.stashTaskView(task.floatingView, shouldStash);
- }
- }
-
- /** Removes all {@link FloatingTaskView} from the layout. */
- public void removeAllTaskViews() {
- int childCount = getChildCount();
- ArrayList<View> viewsToRemove = new ArrayList<>();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i) instanceof FloatingTaskView) {
- viewsToRemove.add(getChildAt(i));
- }
- }
- for (View v : viewsToRemove) {
- removeView(v);
- }
- }
-
- /** Returns the number of task views in the layout. */
- public int getTaskViewCount() {
- int taskViewCount = 0;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i) instanceof FloatingTaskView) {
- taskViewCount++;
- }
- }
- return taskViewCount;
- }
-
- /**
- * Called when the task view is un-stuck from the dismiss target.
- * @param v the task view being moved.
- * @param velX the x velocity of the motion event.
- * @param velY the y velocity of the motion event.
- * @param wasFlungOut true if the user flung the task view out of the dismiss target (i.e. there
- * was an 'up' event), otherwise the user is still dragging.
- */
- public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
- boolean wasFlungOut) {
- mTouchHandler.onUnstuckFromTarget(v, velX, velY, wasFlungOut);
- }
-
- /**
- * Updates dimensions and applies them to any task views.
- */
- public void updateSizes() {
- if (mDismissController != null) {
- mDismissController.updateSizes();
- }
-
- mOverhangWhenStashed = getResources().getDimensionPixelSize(
- R.dimen.floating_task_stash_offset);
- mVerticalPadding = getResources().getDimensionPixelSize(
- R.dimen.floating_task_vertical_padding);
-
- WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
- WindowInsets windowInsets = windowMetrics.getWindowInsets();
- Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()
- | WindowInsets.Type.statusBars()
- | WindowInsets.Type.displayCutout());
- Rect bounds = windowMetrics.getBounds();
- mPositionRect.set(bounds.left + insets.left,
- bounds.top + insets.top + mVerticalPadding,
- bounds.right - insets.right,
- bounds.bottom - insets.bottom - mVerticalPadding);
-
- int taskViewWidth = Math.max(bounds.height(), bounds.width());
- int taskViewHeight = Math.min(bounds.height(), bounds.width());
- taskViewHeight = taskViewHeight - (insets.top + insets.bottom + (mVerticalPadding * 2));
- mTaskViewSize.set((int) (taskViewWidth * START_SIZE_WIDTH_PERCENT), taskViewHeight);
- mDefaultStartPosition.set(mPositionRect.left, mPositionRect.top);
-
- // Update existing views
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i) instanceof FloatingTaskView) {
- FloatingTaskView child = (FloatingTaskView) getChildAt(i);
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.width = mTaskViewSize.x;
- lp.height = mTaskViewSize.y;
- child.setLayoutParams(lp);
- updateTaskViewPosition(child);
- }
- }
- }
-
- /** Returns the first floating task view in the layout. (Currently only ever 1 view). */
- @Nullable
- public FloatingTaskView getFirstTaskView() {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child instanceof FloatingTaskView) {
- return (FloatingTaskView) child;
- }
- }
- return null;
- }
-
- private void updateTaskViewPosition(FloatingTaskView floatingView) {
- Point lastPosition = mController.getLastPosition();
- if (lastPosition.x == -1 && lastPosition.y == -1) {
- floatingView.setX(mDefaultStartPosition.x);
- floatingView.setY(mDefaultStartPosition.y);
- } else {
- floatingView.setX(lastPosition.x);
- floatingView.setY(lastPosition.y);
- }
- if (mTouchHandler.isStashedPosition(floatingView)) {
- floatingView.setStashed(true);
- }
- floatingView.updateLocation();
- }
-
- /**
- * Updates the area of the screen that shouldn't allow the back gesture due to the placement
- * of task view (i.e. when task view is stashed on an edge, tapping or swiping that edge would
- * un-stash the task view instead of performing the back gesture).
- */
- private void updateSystemGestureExclusion() {
- Rect excludeZone = mSystemGestureExclusionRects.get(0);
- FloatingTaskView floatingTaskView = getFirstTaskView();
- if (floatingTaskView != null && floatingTaskView.isStashed()) {
- excludeZone.set(floatingTaskView.getLeft(),
- floatingTaskView.getTop(),
- floatingTaskView.getRight(),
- floatingTaskView.getBottom());
- excludeZone.offset((int) (floatingTaskView.getTranslationX()),
- (int) (floatingTaskView.getTranslationY()));
- setSystemGestureExclusionRects(mSystemGestureExclusionRects);
- } else {
- excludeZone.setEmpty();
- setSystemGestureExclusionRects(Collections.emptyList());
- }
- }
-
- /**
- * Fills in the touchable region for floating windows. This is used by WindowManager to
- * decide which touch events go to the floating windows.
- */
- private void getTouchableRegion(Region outRegion) {
- int childCount = getChildCount();
- Rect temp = new Rect();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child instanceof FloatingTaskView) {
- child.getBoundsOnScreen(temp);
- outRegion.op(temp, Region.Op.UNION);
- }
- }
- }
-
- /**
- * Implementation of the touch handler. Animates the task view based on touch events.
- */
- private class TouchHandlerImpl implements FloatingTaskTouchHandler {
- /**
- * The view can be stashed by swiping it towards the current edge or moving it there. If
- * the view gets moved in a way that is not one of these gestures, this is flipped to false.
- */
- private boolean mCanStash = true;
- /**
- * This is used to indicate that the view has been un-stuck from the dismiss target and
- * needs to spring to the current touch location.
- */
- // TODO: implement this behavior
- private boolean mSpringToTouchOnNextMotionEvent = false;
-
- private ArrayList<FlingAnimation> mFlingAnimations;
- private ViewPropertyAnimator mViewPropertyAnimation;
-
- private float mViewInitialX;
- private float mViewInitialY;
-
- private float[] mMinMax = new float[2];
-
- @Override
- public void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY) {
- mCanStash = true;
- mViewInitialX = viewInitialX;
- mViewInitialY = viewInitialY;
- mDismissController.setUpMagneticObject(v);
- mDismissController.passEventToMagnetizedObject(ev);
- }
-
- @Override
- public void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float dx, float dy) {
- // Shows the magnetic dismiss target if needed.
- mDismissController.showDismiss(/* show= */ true);
-
- // Send it to magnetic target first.
- if (mDismissController.passEventToMagnetizedObject(ev)) {
- v.setStashed(false);
- mCanStash = true;
-
- return;
- }
-
- // If we're here magnetic target didn't want it so move as per normal.
-
- v.setTranslationX(capX(v, mViewInitialX + dx, /* isMoving= */ true));
- v.setTranslationY(capY(v, mViewInitialY + dy));
- if (v.isStashed()) {
- // Check if we've moved far enough to be not stashed.
- final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
- final boolean viewInitiallyOnLeftSide = mViewInitialX < centerX;
- if (viewInitiallyOnLeftSide) {
- if (v.getTranslationX() > mPositionRect.left) {
- v.setStashed(false);
- mCanStash = true;
- }
- } else if (v.getTranslationX() + v.getWidth() < mPositionRect.right) {
- v.setStashed(false);
- mCanStash = true;
- }
- }
- }
-
- // Reference for math / values: StackAnimationController#flingStackThenSpringToEdge.
- // TODO clean up the code here, pretty hard to comprehend
- // TODO code here doesn't work the best when in portrait (e.g. can't fling up/down on edges)
- @Override
- public void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev,
- float dx, float dy, float velX, float velY) {
-
- // Send it to magnetic target first.
- if (mDismissController.passEventToMagnetizedObject(ev)) {
- v.setStashed(false);
- return;
- }
- mDismissController.showDismiss(/* show= */ false);
-
- // If we're here magnetic target didn't want it so handle up as per normal.
-
- final float x = capX(v, mViewInitialX + dx, /* isMoving= */ false);
- final float centerX = mPositionRect.centerX();
- final boolean viewInitiallyOnLeftSide = mViewInitialX + v.getWidth() < centerX;
- final boolean viewOnLeftSide = x + v.getWidth() < centerX;
- final boolean isFling = Math.abs(velX) > ESCAPE_VELOCITY;
- final boolean isFlingLeft = isFling && velX < ESCAPE_VELOCITY;
- // TODO: check velX here sometimes it doesn't stash on move when I think it should
- final boolean shouldStashFromMove =
- (velX < 0 && v.getTranslationX() < mPositionRect.left)
- || (velX > 0
- && v.getTranslationX() + v.getWidth() > mPositionRect.right);
- final boolean shouldStashFromFling = viewInitiallyOnLeftSide == viewOnLeftSide
- && isFling
- && ((viewOnLeftSide && velX < ESCAPE_VELOCITY)
- || (!viewOnLeftSide && velX > ESCAPE_VELOCITY));
- final boolean shouldStash = mCanStash && (shouldStashFromFling || shouldStashFromMove);
-
- ProtoLog.d(WM_SHELL_FLOATING_APPS,
- "shouldStash=%s shouldStashFromFling=%s shouldStashFromMove=%s"
- + " viewInitiallyOnLeftSide=%s viewOnLeftSide=%s isFling=%s velX=%f"
- + " isStashed=%s", shouldStash, shouldStashFromFling, shouldStashFromMove,
- viewInitiallyOnLeftSide, viewOnLeftSide, isFling, velX, v.isStashed());
-
- if (v.isStashed()) {
- mMinMax[0] = viewOnLeftSide
- ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
- : mPositionRect.right - v.getWidth();
- mMinMax[1] = viewOnLeftSide
- ? mPositionRect.left
- : mPositionRect.right - mOverhangWhenStashed;
- } else {
- populateMinMax(v, viewOnLeftSide, shouldStash, mMinMax);
- }
-
- boolean movingLeft = isFling ? isFlingLeft : viewOnLeftSide;
- float destinationRelativeX = movingLeft
- ? mMinMax[0]
- : mMinMax[1];
-
- // TODO: why is this necessary / when does this happen?
- if (mMinMax[1] < v.getTranslationX()) {
- mMinMax[1] = v.getTranslationX();
- }
- if (v.getTranslationX() < mMinMax[0]) {
- mMinMax[0] = v.getTranslationX();
- }
-
- // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
- // so that it'll make it all the way to the side of the screen.
- final float minimumVelocityToReachEdge =
- getMinimumVelocityToReachEdge(v, destinationRelativeX);
- final float startXVelocity = movingLeft
- ? Math.min(minimumVelocityToReachEdge, velX)
- : Math.max(minimumVelocityToReachEdge, velX);
-
- cancelAnyAnimations(v);
-
- mFlingAnimations = getAnimationForUpEvent(v, shouldStash,
- startXVelocity, mMinMax[0], mMinMax[1], destinationRelativeX);
- for (int i = 0; i < mFlingAnimations.size(); i++) {
- mFlingAnimations.get(i).start();
- }
- }
-
- @Override
- public void onClick(@NonNull FloatingTaskView v) {
- if (v.isStashed()) {
- final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
- final boolean viewOnLeftSide = v.getTranslationX() < centerX;
- final float destinationRelativeX = viewOnLeftSide
- ? mPositionRect.left
- : mPositionRect.right - v.getWidth();
- final float minimumVelocityToReachEdge =
- getMinimumVelocityToReachEdge(v, destinationRelativeX);
- populateMinMax(v, viewOnLeftSide, /* stashed= */ true, mMinMax);
-
- cancelAnyAnimations(v);
-
- FlingAnimation flingAnimation = new FlingAnimation(v,
- DynamicAnimation.TRANSLATION_X);
- flingAnimation.setFriction(FLING_FRICTION)
- .setStartVelocity(minimumVelocityToReachEdge)
- .setMinValue(mMinMax[0])
- .setMaxValue(mMinMax[1])
- .addEndListener((animation, canceled, value, velocity) -> {
- if (canceled) return;
- mController.setLastPosition((int) v.getTranslationX(),
- (int) v.getTranslationY());
- v.setStashed(false);
- v.updateLocation();
- });
- mFlingAnimations = new ArrayList<>();
- mFlingAnimations.add(flingAnimation);
- flingAnimation.start();
- }
- }
-
- public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY,
- boolean wasFlungOut) {
- if (wasFlungOut) {
- snapTaskViewToEdge(v, velX, /* shouldStash= */ false);
- } else {
- // TODO: use this for something / to spring the view to the touch location
- mSpringToTouchOnNextMotionEvent = true;
- }
- }
-
- public void stashTaskView(FloatingTaskView v, boolean shouldStash) {
- if (v.isStashed() == shouldStash) {
- return;
- }
- final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f);
- final boolean viewOnLeftSide = v.getTranslationX() < centerX;
- snapTaskViewToEdge(v, viewOnLeftSide ? -ESCAPE_VELOCITY : ESCAPE_VELOCITY, shouldStash);
- }
-
- public boolean isStashedPosition(View v) {
- return v.getTranslationX() < mPositionRect.left
- || v.getTranslationX() + v.getWidth() > mPositionRect.right;
- }
-
- // TODO: a lot of this is duplicated in onUp -- can it be unified?
- private void snapTaskViewToEdge(FloatingTaskView v, float velX, boolean shouldStash) {
- final boolean movingLeft = velX < ESCAPE_VELOCITY;
- populateMinMax(v, movingLeft, shouldStash, mMinMax);
- float destinationRelativeX = movingLeft
- ? mMinMax[0]
- : mMinMax[1];
-
- // TODO: why is this necessary / when does this happen?
- if (mMinMax[1] < v.getTranslationX()) {
- mMinMax[1] = v.getTranslationX();
- }
- if (v.getTranslationX() < mMinMax[0]) {
- mMinMax[0] = v.getTranslationX();
- }
-
- // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity
- // so that it'll make it all the way to the side of the screen.
- final float minimumVelocityToReachEdge =
- getMinimumVelocityToReachEdge(v, destinationRelativeX);
- final float startXVelocity = movingLeft
- ? Math.min(minimumVelocityToReachEdge, velX)
- : Math.max(minimumVelocityToReachEdge, velX);
-
- cancelAnyAnimations(v);
-
- mFlingAnimations = getAnimationForUpEvent(v,
- shouldStash, startXVelocity, mMinMax[0], mMinMax[1],
- destinationRelativeX);
- for (int i = 0; i < mFlingAnimations.size(); i++) {
- mFlingAnimations.get(i).start();
- }
- }
-
- private void cancelAnyAnimations(FloatingTaskView v) {
- if (mFlingAnimations != null) {
- for (int i = 0; i < mFlingAnimations.size(); i++) {
- if (mFlingAnimations.get(i).isRunning()) {
- mFlingAnimations.get(i).cancel();
- }
- }
- }
- if (mViewPropertyAnimation != null) {
- mViewPropertyAnimation.cancel();
- mViewPropertyAnimation = null;
- }
- }
-
- private ArrayList<FlingAnimation> getAnimationForUpEvent(FloatingTaskView v,
- boolean shouldStash, float startVelX, float minValue, float maxValue,
- float destinationRelativeX) {
- final float ty = v.getTranslationY();
- final ArrayList<FlingAnimation> animations = new ArrayList<>();
- if (ty != capY(v, ty)) {
- // The view was being dismissed so the Y is out of bounds, need to animate that.
- FlingAnimation yFlingAnimation = new FlingAnimation(v,
- DynamicAnimation.TRANSLATION_Y);
- yFlingAnimation.setFriction(FLING_FRICTION)
- .setStartVelocity(startVelX)
- .setMinValue(mPositionRect.top)
- .setMaxValue(mPositionRect.bottom - mTaskViewSize.y);
- animations.add(yFlingAnimation);
- }
- FlingAnimation flingAnimation = new FlingAnimation(v, DynamicAnimation.TRANSLATION_X);
- flingAnimation.setFriction(FLING_FRICTION)
- .setStartVelocity(startVelX)
- .setMinValue(minValue)
- .setMaxValue(maxValue)
- .addEndListener((animation, canceled, value, velocity) -> {
- if (canceled) return;
- Runnable endAction = () -> {
- v.setStashed(shouldStash);
- v.updateLocation();
- if (!v.isStashed()) {
- mController.setLastPosition((int) v.getTranslationX(),
- (int) v.getTranslationY());
- }
- };
- if (!shouldStash) {
- final int xTranslation = (int) v.getTranslationX();
- if (xTranslation != destinationRelativeX) {
- // TODO: this animation doesn't feel great, should figure out
- // a better way to do this or remove the need for it all together.
- mViewPropertyAnimation = v.animate()
- .translationX(destinationRelativeX)
- .setListener(getAnimationListener(endAction));
- mViewPropertyAnimation.start();
- } else {
- endAction.run();
- }
- } else {
- endAction.run();
- }
- });
- animations.add(flingAnimation);
- return animations;
- }
-
- private AnimatorListenerAdapter getAnimationListener(Runnable endAction) {
- return new AnimatorListenerAdapter() {
- boolean translationCanceled = false;
- @Override
- public void onAnimationCancel(Animator animation) {
- super.onAnimationCancel(animation);
- translationCanceled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- if (!translationCanceled) {
- endAction.run();
- }
- }
- };
- }
-
- private void populateMinMax(FloatingTaskView v, boolean onLeft, boolean shouldStash,
- float[] out) {
- if (shouldStash) {
- out[0] = onLeft
- ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed
- : mPositionRect.right - v.getWidth();
- out[1] = onLeft
- ? mPositionRect.left
- : mPositionRect.right - mOverhangWhenStashed;
- } else {
- out[0] = mPositionRect.left;
- out[1] = mPositionRect.right - mTaskViewSize.x;
- }
- }
-
- private float getMinimumVelocityToReachEdge(FloatingTaskView v,
- float destinationRelativeX) {
- // Minimum velocity required for the view to make it to the targeted side of the screen,
- // taking friction into account (4.2f is the number that friction scalars are multiplied
- // by in DynamicAnimation.DragForce). This is an estimate and could be slightly off, the
- // animation at the end will ensure that it reaches the destination X regardless.
- return (destinationRelativeX - v.getTranslationX()) * (FLING_FRICTION * 4.2f);
- }
-
- private float capX(FloatingTaskView v, float x, boolean isMoving) {
- final int width = v.getWidth();
- if (v.isStashed() || isMoving) {
- if (x < mPositionRect.left - v.getWidth() + mOverhangWhenStashed) {
- return mPositionRect.left - v.getWidth() + mOverhangWhenStashed;
- }
- if (x > mPositionRect.right - mOverhangWhenStashed) {
- return mPositionRect.right - mOverhangWhenStashed;
- }
- } else {
- if (x < mPositionRect.left) {
- return mPositionRect.left;
- }
- if (x > mPositionRect.right - width) {
- return mPositionRect.right - width;
- }
- }
- return x;
- }
-
- private float capY(FloatingTaskView v, float y) {
- final int height = v.getHeight();
- if (y < mPositionRect.top) {
- return mPositionRect.top;
- }
- if (y > mPositionRect.bottom - height) {
- return mPositionRect.bottom - height;
- }
- return y;
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
deleted file mode 100644
index 581204a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.floating.views;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskView;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.bubbles.RelativeTouchListener;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.floating.FloatingTasksController;
-
-/**
- * A view that holds a floating task using {@link TaskView} along with additional UI to manage
- * the task.
- */
-public class FloatingTaskView extends FrameLayout {
-
- private static final String TAG = FloatingTaskView.class.getSimpleName();
-
- private FloatingTasksController mController;
-
- private FloatingMenuView mMenuView;
- private int mMenuHeight;
- private TaskView mTaskView;
-
- private float mCornerRadius = 0f;
- private int mBackgroundColor;
-
- private FloatingTasksController.Task mTask;
-
- private boolean mIsStashed;
-
- /**
- * Creates a floating task view.
- *
- * @param context the context to use.
- * @param controller the controller to notify about changes in the floating task (e.g. removal).
- */
- public FloatingTaskView(Context context, FloatingTasksController controller) {
- super(context);
- mController = controller;
- setElevation(getResources().getDimensionPixelSize(R.dimen.floating_task_elevation));
- mMenuHeight = context.getResources().getDimensionPixelSize(R.dimen.floating_task_menu_size);
- mMenuView = new FloatingMenuView(context);
- addView(mMenuView);
-
- applyThemeAttrs();
-
- setClipToOutline(true);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
- }
- });
- }
-
- // TODO: call this when theme/config changes
- void applyThemeAttrs() {
- boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
- mContext.getResources());
- final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
- android.R.attr.dialogCornerRadius,
- android.R.attr.colorBackgroundFloating});
- mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0;
- mCornerRadius = mCornerRadius / 2f;
- mBackgroundColor = ta.getColor(1, Color.WHITE);
-
- ta.recycle();
-
- mMenuView.setCornerRadius(mCornerRadius);
- mMenuHeight = getResources().getDimensionPixelSize(
- R.dimen.floating_task_menu_size);
-
- if (mTaskView != null) {
- mTaskView.setCornerRadius(mCornerRadius);
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
-
- // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
- int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
- measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
- MeasureSpec.getMode(heightMeasureSpec)));
-
- if (mTaskView != null) {
- int taskViewHeight = height - menuViewHeight;
- measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight,
- MeasureSpec.getMode(heightMeasureSpec)));
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // Drag handle above
- final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
- mMenuView.layout(l, t, r, dragHandleBottom);
- if (mTaskView != null) {
- // Subtract radius so that the menu extends behind the rounded corners of TaskView.
- mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
- dragHandleBottom + mTaskView.getMeasuredHeight());
- }
- }
-
- /**
- * Constructs the TaskView to display the task. Must be called for {@link #startTask} to work.
- */
- public void createTaskView(Context context, ShellTaskOrganizer organizer,
- TaskViewTransitions transitions, SyncTransactionQueue syncQueue) {
- mTaskView = new TaskView(context, organizer, transitions, syncQueue);
- addView(mTaskView);
- mTaskView.setEnableSurfaceClipping(true);
- mTaskView.setCornerRadius(mCornerRadius);
- }
-
- /**
- * Starts the provided task in the TaskView, if the TaskView exists. This should be called after
- * {@link #createTaskView}.
- */
- public void startTask(@ShellMainThread ShellExecutor executor,
- FloatingTasksController.Task task) {
- if (mTaskView == null) {
- Log.e(TAG, "starting task before creating the view!");
- return;
- }
- mTask = task;
- mTaskView.setListener(executor, mTaskViewListener);
- }
-
- /**
- * Sets the touch handler for the view.
- *
- * @param handler the touch handler for the view.
- */
- public void setTouchHandler(FloatingTaskLayer.FloatingTaskTouchHandler handler) {
- setOnTouchListener(new RelativeTouchListener() {
- @Override
- public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
- handler.onDown(FloatingTaskView.this, ev, v.getTranslationX(), v.getTranslationY());
- return true;
- }
-
- @Override
- public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY, float dx, float dy) {
- handler.onMove(FloatingTaskView.this, ev, dx, dy);
- }
-
- @Override
- public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY, float dx, float dy, float velX, float velY) {
- handler.onUp(FloatingTaskView.this, ev, dx, dy, velX, velY);
- }
- });
- setOnClickListener(view -> {
- handler.onClick(FloatingTaskView.this);
- });
-
- mMenuView.addMenuItem(null, view -> {
- if (mIsStashed) {
- // If we're stashed all clicks un-stash.
- handler.onClick(FloatingTaskView.this);
- }
- });
- }
-
- private void setContentVisibility(boolean visible) {
- if (mTaskView == null) return;
- mTaskView.setAlpha(visible ? 1f : 0f);
- }
-
- /**
- * Sets the alpha of both this view and the TaskView.
- */
- public void setTaskViewAlpha(float alpha) {
- if (mTaskView != null) {
- mTaskView.setAlpha(alpha);
- }
- setAlpha(alpha);
- }
-
- /**
- * Call when the location or size of the view has changed to update TaskView.
- */
- public void updateLocation() {
- if (mTaskView == null) return;
- mTaskView.onLocationChanged();
- }
-
- private void updateMenuColor() {
- ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
- int color = info != null ? info.taskDescription.getBackgroundColor() : -1;
- if (color != -1) {
- mMenuView.setBackgroundColor(color);
- } else {
- mMenuView.setBackgroundColor(mBackgroundColor);
- }
- }
-
- /**
- * Sets whether the view is stashed or not.
- *
- * Also updates the touchable area based on this. If the view is stashed we don't direct taps
- * on the activity to the activity, instead a tap will un-stash the view.
- */
- public void setStashed(boolean isStashed) {
- if (mIsStashed != isStashed) {
- mIsStashed = isStashed;
- if (mTaskView == null) {
- return;
- }
- updateObscuredTouchRect();
- }
- }
-
- /** Whether the view is stashed at the edge of the screen or not. **/
- public boolean isStashed() {
- return mIsStashed;
- }
-
- private void updateObscuredTouchRect() {
- if (mIsStashed) {
- Rect tmpRect = new Rect();
- getBoundsOnScreen(tmpRect);
- mTaskView.setObscuredTouchRect(tmpRect);
- } else {
- mTaskView.setObscuredTouchRect(null);
- }
- }
-
- /**
- * Whether the task needs to be restarted, this can happen when {@link #cleanUpTaskView()} has
- * been called on this view or if
- * {@link #startTask(ShellExecutor, FloatingTasksController.Task)} was never called.
- */
- public boolean needsTaskStarted() {
- // If the task needs to be restarted then TaskView would have been cleaned up.
- return mTaskView == null;
- }
-
- /** Call this when the floating task activity is no longer in use. */
- public void cleanUpTaskView() {
- if (mTask != null && mTask.taskId != INVALID_TASK_ID) {
- try {
- ActivityTaskManager.getService().removeTask(mTask.taskId);
- } catch (RemoteException e) {
- Log.e(TAG, e.getMessage());
- }
- }
- if (mTaskView != null) {
- mTaskView.release();
- removeView(mTaskView);
- mTaskView = null;
- }
- }
-
- // TODO: use task background colour / how to get the taskInfo ?
- private static int getDragBarColor(ActivityManager.RunningTaskInfo taskInfo) {
- final int taskBgColor = taskInfo.taskDescription.getStatusBarColor();
- return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
- }
-
- private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
- private boolean mInitialized = false;
- private boolean mDestroyed = false;
-
- @Override
- public void onInitialized() {
- if (mDestroyed || mInitialized) {
- return;
- }
- // Custom options so there is no activity transition animation
- ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
- /* enterResId= */ 0, /* exitResId= */ 0);
-
- Rect launchBounds = new Rect();
- mTaskView.getBoundsOnScreen(launchBounds);
-
- try {
- options.setTaskAlwaysOnTop(true);
- if (mTask.intent != null) {
- Intent fillInIntent = new Intent();
- // Apply flags to make behaviour match documentLaunchMode=always.
- fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-
- PendingIntent pi = PendingIntent.getActivity(mContext, 0, mTask.intent,
- PendingIntent.FLAG_MUTABLE,
- null);
- mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
- } else {
- ProtoLog.e(WM_SHELL_FLOATING_APPS, "Tried to start a task with null intent");
- }
- } catch (RuntimeException e) {
- ProtoLog.e(WM_SHELL_FLOATING_APPS, "Exception while starting task: %s",
- e.getMessage());
- mController.removeTask();
- }
- mInitialized = true;
- }
-
- @Override
- public void onReleased() {
- mDestroyed = true;
- }
-
- @Override
- public void onTaskCreated(int taskId, ComponentName name) {
- mTask.taskId = taskId;
- updateMenuColor();
- setContentVisibility(true);
- }
-
- @Override
- public void onTaskVisibilityChanged(int taskId, boolean visible) {
- setContentVisibility(visible);
- }
-
- @Override
- public void onTaskRemovalStarted(int taskId) {
- // Must post because this is called from a binder thread.
- post(() -> {
- mController.removeTask();
- cleanUpTaskView();
- });
- }
-
- @Override
- public void onBackPressedOnTaskRoot(int taskId) {
- if (mTask.taskId == taskId && !mIsStashed) {
- // TODO: is removing the window the desired behavior?
- post(() -> mController.removeTask());
- }
- }
- };
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 616d447..d28a9f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -612,9 +612,24 @@
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
+ int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChanged(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
+ int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
+ if (!mEnablePipKeepClearAlgorithm) {
+ int pipTop = mPipBoundsState.getBounds().top;
+ int diff = newMaxMovementBound - oldMaxMovementBound;
+ if (diff < 0 && pipTop > newMaxMovementBound) {
+ // bottom inset has increased, move PiP up if it is too low
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
+ newMaxMovementBound - pipTop);
+ }
+ if (diff > 0 && oldMaxMovementBound == pipTop) {
+ // bottom inset has decreased, move PiP down if it was by the edge
+ mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
+ }
+ }
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 975d4bb..a9a97be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -427,7 +427,7 @@
// If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
// occluded by the IME or shelf.
if (fromImeAdjustment || fromShelfAdjustment) {
- if (mTouchState.isUserInteracting()) {
+ if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
} else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 89205a6..519ec14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -175,6 +175,9 @@
}
private void onInit() {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mOrganizer.shareTransactionQueue();
+ }
mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
this::createExternalInterface, this);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 6f1ff99..8765ad1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -45,14 +45,23 @@
fun FlickerTestParameter.splitScreenEntered(
component1: IComponentMatcher,
component2: IComponentMatcher,
- fromOtherApp: Boolean
+ fromOtherApp: Boolean,
+ appExistAtStart: Boolean = true
) {
if (fromOtherApp) {
- appWindowIsInvisibleAtStart(component1)
+ if (appExistAtStart) {
+ appWindowIsInvisibleAtStart(component1)
+ } else {
+ appWindowIsNotContainAtStart(component1)
+ }
} else {
appWindowIsVisibleAtStart(component1)
}
- appWindowIsInvisibleAtStart(component2)
+ if (appExistAtStart) {
+ appWindowIsInvisibleAtStart(component2)
+ } else {
+ appWindowIsNotContainAtStart(component2)
+ }
splitScreenDividerIsInvisibleAtStart()
appWindowIsVisibleAtEnd(component1)
@@ -315,6 +324,10 @@
assertWmEnd { this.isAppWindowInvisible(component) }
}
+fun FlickerTestParameter.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+ assertWmStart { this.notContains(component) }
+}
+
fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
assertWm { this.isAppWindowVisible(component) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 1a29193..e9c765a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -23,7 +23,6 @@
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
@@ -78,7 +77,8 @@
@Postsubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp,
+ fromOtherApp = false, appExistAtStart = false)
@Postsubmit
@Test
@@ -108,7 +108,15 @@
@Postsubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() {
+ testSpec.assertWm {
+ this.notContains(secondaryApp)
+ .then()
+ .isAppWindowInvisible(secondaryApp, isOptional = true)
+ .then()
+ .isAppWindowVisible(secondaryApp)
+ }
+ }
/** {@inheritDoc} */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
deleted file mode 100644
index d378a17..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.floating;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.floating.FloatingTasksController.SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.WindowMetrics;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.floating.views.FloatingTaskLayer;
-import com.android.wm.shell.sysui.ShellCommandHandler;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-/**
- * Tests for the floating tasks controller.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FloatingTasksControllerTest extends ShellTestCase {
- // Some behavior in the controller constructor is dependent on this so we can only
- // validate if it's working for the real value for those things.
- private static final boolean FLOATING_TASKS_ACTUALLY_ENABLED =
- SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false);
-
- @Mock private ShellInit mShellInit;
- @Mock private ShellController mShellController;
- @Mock private WindowManager mWindowManager;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Captor private ArgumentCaptor<FloatingTaskLayer> mFloatingTaskLayerCaptor;
-
- private FloatingTasksController mController;
-
- @Before
- public void setUp() throws RemoteException {
- MockitoAnnotations.initMocks(this);
-
- WindowMetrics windowMetrics = mock(WindowMetrics.class);
- WindowInsets windowInsets = mock(WindowInsets.class);
- Insets insets = Insets.of(0, 0, 0, 0);
- when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics);
- when(windowMetrics.getWindowInsets()).thenReturn(windowInsets);
- when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1000, 1000));
- when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(insets);
-
- // For the purposes of this test, just run everything synchronously
- ShellExecutor shellExecutor = new TestShellExecutor();
- when(mTaskOrganizer.getExecutor()).thenReturn(shellExecutor);
- }
-
- @After
- public void tearDown() {
- if (mController != null) {
- mController.removeTask();
- mController = null;
- }
- }
-
- private void setUpTabletConfig() {
- Configuration config = mock(Configuration.class);
- config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET;
- mController.setConfig(config);
- }
-
- private void setUpPhoneConfig() {
- Configuration config = mock(Configuration.class);
- config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET - 1;
- mController.setConfig(config);
- }
-
- private void createController() {
- mController = new FloatingTasksController(mContext,
- mShellInit,
- mShellController,
- mock(ShellCommandHandler.class),
- Optional.empty(),
- mWindowManager,
- mTaskOrganizer,
- mock(TaskViewTransitions.class),
- mock(ShellExecutor.class),
- mock(ShellExecutor.class),
- mock(SyncTransactionQueue.class));
- spyOn(mController);
- }
-
- //
- // Shell specific
- //
- @Test
- public void instantiateController_addInitCallback() {
- if (FLOATING_TASKS_ACTUALLY_ENABLED) {
- createController();
- setUpTabletConfig();
-
- verify(mShellInit, times(1)).addInitCallback(any(), any());
- }
- }
-
- @Test
- public void instantiateController_doesntAddInitCallback() {
- if (!FLOATING_TASKS_ACTUALLY_ENABLED) {
- createController();
-
- verify(mShellInit, never()).addInitCallback(any(), any());
- }
- }
-
- @Test
- public void onInit_registerConfigChangeListener() {
- if (FLOATING_TASKS_ACTUALLY_ENABLED) {
- createController();
- setUpTabletConfig();
- mController.onInit();
-
- verify(mShellController, times(1)).addConfigurationChangeListener(any());
- }
- }
-
- @Test
- public void onInit_addExternalInterface() {
- if (FLOATING_TASKS_ACTUALLY_ENABLED) {
- createController();
- setUpTabletConfig();
- mController.onInit();
-
- verify(mShellController, times(1)).addExternalInterface(
- ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any());
- }
- }
-
- //
- // Tests for floating layer, which is only available for tablets.
- //
-
- @Test
- public void testIsFloatingLayerAvailable_true() {
- createController();
- setUpTabletConfig();
- assertThat(mController.isFloatingLayerAvailable()).isTrue();
- }
-
- @Test
- public void testIsFloatingLayerAvailable_false() {
- createController();
- setUpPhoneConfig();
- assertThat(mController.isFloatingLayerAvailable()).isFalse();
- }
-
- //
- // Tests for floating tasks being enabled, guarded by sysprop flag.
- //
-
- @Test
- public void testIsFloatingTasksEnabled_true() {
- createController();
- mController.setFloatingTasksEnabled(true);
- setUpTabletConfig();
- assertThat(mController.isFloatingTasksEnabled()).isTrue();
- }
-
- @Test
- public void testIsFloatingTasksEnabled_false() {
- createController();
- mController.setFloatingTasksEnabled(false);
- setUpTabletConfig();
- assertThat(mController.isFloatingTasksEnabled()).isFalse();
- }
-
- //
- // Tests for behavior depending on flags
- //
-
- @Test
- public void testShowTaskIntent_enabled() {
- createController();
- mController.setFloatingTasksEnabled(true);
- setUpTabletConfig();
-
- mController.showTask(mock(Intent.class));
- verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
- assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
- }
-
- @Test
- public void testShowTaskIntent_notEnabled() {
- createController();
- mController.setFloatingTasksEnabled(false);
- setUpTabletConfig();
-
- mController.showTask(mock(Intent.class));
- verify(mWindowManager, never()).addView(any(), any());
- }
-
- @Test
- public void testRemoveTask() {
- createController();
- mController.setFloatingTasksEnabled(true);
- setUpTabletConfig();
-
- mController.showTask(mock(Intent.class));
- verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any());
- assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1);
-
- mController.removeTask();
- verify(mWindowManager).removeView(mFloatingTaskLayerCaptor.capture());
- assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(0);
- }
-}
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index 15be80c..d1cfd03 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <memory>
#include <utility>
#include "androidfw/CursorWindow.h"
@@ -184,7 +185,7 @@
ASSERT_EQ(w->allocRow(), OK);
// Scratch buffer that will fit before inflation
- void* buf = malloc(kHalfInlineSize);
+ char buf[kHalfInlineSize];
// Store simple value
ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -262,7 +263,7 @@
ASSERT_EQ(w->allocRow(), OK);
// Scratch buffer that will fit before inflation
- void* buf = malloc(kHalfInlineSize);
+ char buf[kHalfInlineSize];
// Store simple value
ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -322,7 +323,8 @@
ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
// Store object that forces inflation
- void* buf = malloc(kGiantSize);
+ std::unique_ptr<char> bufPtr(new char[kGiantSize]);
+ void* buf = bufPtr.get();
memset(buf, 42, kGiantSize);
ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 29f3773..cccc0f81 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -678,6 +678,7 @@
srcs: [
"tests/unit/main.cpp",
"tests/unit/ABitmapTests.cpp",
+ "tests/unit/AutoBackendTextureReleaseTests.cpp",
"tests/unit/CacheManagerTests.cpp",
"tests/unit/CanvasContextTests.cpp",
"tests/unit/CanvasOpTests.cpp",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index ef5eacb..b656b6a 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -32,9 +32,17 @@
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
GrBackendFormat backendFormat =
GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+ LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
+ __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
+ ", AHardwareBuffer_Format==%" PRIu32 ".",
+ static_cast<int>(context->backend()), desc.format);
mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
createProtectedImage, backendFormat, false);
+ LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
+ __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
+ ", protected==%d",
+ desc.width, desc.height, createProtectedImage);
}
void AutoBackendTextureRelease::unref(bool releaseImage) {
@@ -74,13 +82,13 @@
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+ // The following ref will be counteracted by Skia calling releaseProc, either during
+ // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
+ // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+ ref();
mImage = SkImage::MakeFromTexture(
context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
- if (mImage.get()) {
- // The following ref will be counteracted by releaseProc, when SkImage is discarded.
- ref();
- }
}
void AutoBackendTextureRelease::newBufferContent(GrDirectContext* context) {
diff --git a/libs/hwui/AutoBackendTextureRelease.h b/libs/hwui/AutoBackendTextureRelease.h
index c9bb767..f0eb2a8 100644
--- a/libs/hwui/AutoBackendTextureRelease.h
+++ b/libs/hwui/AutoBackendTextureRelease.h
@@ -25,6 +25,9 @@
namespace android {
namespace uirenderer {
+// Friend TestUtils serves as a proxy for any test cases that require access to private members.
+class TestUtils;
+
/**
* AutoBackendTextureRelease manages EglImage/VkImage lifetime. It is a ref-counted object
* that keeps GPU resources alive until the last SkImage object using them is destroyed.
@@ -66,6 +69,9 @@
// mImage is the SkImage created from mBackendTexture.
sk_sp<SkImage> mImage;
+
+ // Friend TestUtils serves as a proxy for any test cases that require access to private members.
+ friend class TestUtils;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 75865c7..9d5c13e 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -16,6 +16,7 @@
#pragma once
+#include <AutoBackendTextureRelease.h>
#include <DisplayList.h>
#include <Matrix.h>
#include <Properties.h>
@@ -293,6 +294,11 @@
static SkRect getClipBounds(const SkCanvas* canvas);
static SkRect getLocalClipBounds(const SkCanvas* canvas);
+ static int getUsageCount(const AutoBackendTextureRelease* textureRelease) {
+ EXPECT_NE(nullptr, textureRelease);
+ return textureRelease->mUsageCount;
+ }
+
struct CallCounts {
int sync = 0;
int contextDestroyed = 0;
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
new file mode 100644
index 0000000..2ec78a4
--- /dev/null
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "AutoBackendTextureRelease.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+AHardwareBuffer* allocHardwareBuffer() {
+ AHardwareBuffer* buffer;
+ AHardwareBuffer_Desc desc = {
+ .width = 16,
+ .height = 16,
+ .layers = 1,
+ .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
+ };
+ constexpr int kSucceeded = 0;
+ int status = AHardwareBuffer_allocate(&desc, &buffer);
+ EXPECT_EQ(kSucceeded, status);
+ return buffer;
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_invalid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_invalid) {
+ AHardwareBuffer* buffer = allocHardwareBuffer();
+ AutoBackendTextureRelease* textureRelease =
+ new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+ EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+ // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+ textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
+
+ EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+ textureRelease->unref(true);
+ AHardwareBuffer_release(buffer);
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_valid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_valid) {
+ AHardwareBuffer* buffer = allocHardwareBuffer();
+ AutoBackendTextureRelease* textureRelease =
+ new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+ EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+ textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, renderThread.getGrContext());
+
+ EXPECT_EQ(2, TestUtils::getUsageCount(textureRelease));
+
+ textureRelease->unref(true);
+ AHardwareBuffer_release(buffer);
+}
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index b38f9ea..98578b2 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -24,6 +24,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -123,20 +125,23 @@
* @hide
*/
public static GnssCapabilities empty() {
- return new GnssCapabilities(0, 0, 0);
+ return new GnssCapabilities(0, 0, 0, new ArrayList<>());
}
private final @TopHalCapabilityFlags int mTopFlags;
private final @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
private final @SubHalPowerCapabilityFlags int mPowerFlags;
+ private final @NonNull List<GnssSignalType> mGnssSignalTypes;
private GnssCapabilities(
@TopHalCapabilityFlags int topFlags,
@SubHalMeasurementCorrectionsCapabilityFlags int measurementCorrectionsFlags,
- @SubHalPowerCapabilityFlags int powerFlags) {
+ @SubHalPowerCapabilityFlags int powerFlags,
+ @NonNull List<GnssSignalType> gnssSignalTypes) {
mTopFlags = topFlags;
mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
mPowerFlags = powerFlags;
+ mGnssSignalTypes = gnssSignalTypes;
}
/**
@@ -148,7 +153,8 @@
if (mTopFlags == flags) {
return this;
} else {
- return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags);
+ return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
+ new ArrayList<>(mGnssSignalTypes));
}
}
@@ -163,7 +169,8 @@
if (mMeasurementCorrectionsFlags == flags) {
return this;
} else {
- return new GnssCapabilities(mTopFlags, flags, mPowerFlags);
+ return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
+ new ArrayList<>(mGnssSignalTypes));
}
}
@@ -177,7 +184,8 @@
if (mPowerFlags == flags) {
return this;
} else {
- return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags);
+ return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
+ new ArrayList<>(mGnssSignalTypes));
}
}
@@ -424,6 +432,14 @@
return (mPowerFlags & SUB_HAL_POWER_CAPABILITY_OTHER_MODES) != 0;
}
+ /**
+ * Returns the list of {@link GnssSignalType}s that the GNSS chipset supports.
+ */
+ @NonNull
+ public List<GnssSignalType> getGnssSignalTypes() {
+ return mGnssSignalTypes;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -441,14 +457,15 @@
@Override
public int hashCode() {
- return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags);
+ return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags, mGnssSignalTypes);
}
public static final @NonNull Creator<GnssCapabilities> CREATOR =
new Creator<GnssCapabilities>() {
@Override
public GnssCapabilities createFromParcel(Parcel in) {
- return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt());
+ return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt(),
+ in.createTypedArrayList(GnssSignalType.CREATOR));
}
@Override
@@ -467,6 +484,7 @@
parcel.writeInt(mTopFlags);
parcel.writeInt(mMeasurementCorrectionsFlags);
parcel.writeInt(mPowerFlags);
+ parcel.writeTypedList(mGnssSignalTypes);
}
@Override
@@ -545,6 +563,9 @@
if (hasPowerOtherModes()) {
builder.append("OTHER_MODES_POWER ");
}
+ if (!mGnssSignalTypes.isEmpty()) {
+ builder.append("signalTypes=").append(mGnssSignalTypes).append(" ");
+ }
if (builder.length() > 1) {
builder.setLength(builder.length() - 1);
} else {
@@ -562,17 +583,20 @@
private @TopHalCapabilityFlags int mTopFlags;
private @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
private @SubHalPowerCapabilityFlags int mPowerFlags;
+ private @NonNull List<GnssSignalType> mGnssSignalTypes;
public Builder() {
mTopFlags = 0;
mMeasurementCorrectionsFlags = 0;
mPowerFlags = 0;
+ mGnssSignalTypes = new ArrayList<>();
}
public Builder(@NonNull GnssCapabilities capabilities) {
mTopFlags = capabilities.mTopFlags;
mMeasurementCorrectionsFlags = capabilities.mMeasurementCorrectionsFlags;
mPowerFlags = capabilities.mPowerFlags;
+ mGnssSignalTypes = capabilities.mGnssSignalTypes;
}
/**
@@ -779,10 +803,19 @@
}
/**
+ * Sets a list of {@link GnssSignalType}.
+ */
+ public @NonNull Builder setGnssSignalTypes(@NonNull List<GnssSignalType> gnssSignalTypes) {
+ mGnssSignalTypes = gnssSignalTypes;
+ return this;
+ }
+
+ /**
* Builds a new GnssCapabilities.
*/
public @NonNull GnssCapabilities build() {
- return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags);
+ return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags,
+ new ArrayList<>(mGnssSignalTypes));
}
private static int setFlag(int value, int flag, boolean set) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/location/java/android/location/GnssSignalType.aidl
similarity index 73%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to location/java/android/location/GnssSignalType.aidl
index 1550ab3..1c43fe5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/location/java/android/location/GnssSignalType.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package android.location;
-parcelable RemoteTransitionCompat;
+parcelable GnssSignalType;
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
new file mode 100644
index 0000000..f9c6e72
--- /dev/null
+++ b/location/java/android/location/GnssSignalType.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * This class represents a GNSS signal type.
+ */
+public final class GnssSignalType implements Parcelable {
+
+ /**
+ * Creates a {@link GnssSignalType} with a full list of parameters.
+ *
+ * @param constellationType the constellation type as defined in
+ * {@link GnssStatus.ConstellationType}
+ * @param carrierFrequencyHz the carrier frequency in Hz
+ * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
+ */
+ @NonNull
+ public static GnssSignalType create(@GnssStatus.ConstellationType int constellationType,
+ @FloatRange(from = 0.0f, fromInclusive = false) double carrierFrequencyHz,
+ @NonNull String codeType) {
+ Preconditions.checkArgument(carrierFrequencyHz > 0,
+ "carrierFrequencyHz must be greater than 0.");
+ Objects.requireNonNull(codeType);
+ return new GnssSignalType(constellationType, carrierFrequencyHz, codeType);
+ }
+
+ @GnssStatus.ConstellationType
+ private final int mConstellationType;
+ @FloatRange(from = 0.0f, fromInclusive = false)
+ private final double mCarrierFrequencyHz;
+ @NonNull
+ private final String mCodeType;
+
+ /**
+ * Creates a {@link GnssSignalType} with a full list of parameters.
+ */
+ private GnssSignalType(@GnssStatus.ConstellationType int constellationType,
+ double carrierFrequencyHz, @NonNull String codeType) {
+ this.mConstellationType = constellationType;
+ this.mCarrierFrequencyHz = carrierFrequencyHz;
+ this.mCodeType = codeType;
+ }
+
+ /** Returns the {@link GnssStatus.ConstellationType}. */
+ @GnssStatus.ConstellationType
+ public int getConstellationType() {
+ return mConstellationType;
+ }
+
+ /** Returns the carrier frequency in Hz. */
+ @FloatRange(from = 0.0f, fromInclusive = false)
+ public double getCarrierFrequencyHz() {
+ return mCarrierFrequencyHz;
+ }
+
+ /**
+ * Return the code type.
+ *
+ * @see GnssMeasurement#getCodeType()
+ */
+ @NonNull
+ public String getCodeType() {
+ return mCodeType;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<GnssSignalType> CREATOR =
+ new Parcelable.Creator<GnssSignalType>() {
+ @Override
+ @NonNull
+ public GnssSignalType createFromParcel(@NonNull Parcel parcel) {
+ return new GnssSignalType(parcel.readInt(), parcel.readDouble(),
+ parcel.readString());
+ }
+
+ @Override
+ public GnssSignalType[] newArray(int i) {
+ return new GnssSignalType[i];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mConstellationType);
+ parcel.writeDouble(mCarrierFrequencyHz);
+ parcel.writeString(mCodeType);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ s.append("GnssSignalType[");
+ s.append(" Constellation=").append(mConstellationType);
+ s.append(", CarrierFrequencyHz=").append(mCarrierFrequencyHz);
+ s.append(", CodeType=").append(mCodeType);
+ s.append(']');
+ return s.toString();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof GnssSignalType) {
+ GnssSignalType other = (GnssSignalType) obj;
+ return mConstellationType == other.mConstellationType
+ && Double.compare(mCarrierFrequencyHz, other.mCarrierFrequencyHz) == 0
+ && mCodeType.equals(other.mCodeType);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mConstellationType, mCarrierFrequencyHz, mCodeType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 60e3a306..980f63b 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -23,6 +23,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.IBinder;
@@ -220,46 +221,59 @@
/**
* @hide
- * Mute state used for anonymization.
+ * Mute state used for the initial state and when API is accessed without permission.
*/
- public static final int PLAYER_MUTE_INVALID = -1;
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_UNKNOWN = -1;
/**
* @hide
* Flag used when muted by master volume.
*/
- public static final int PLAYER_MUTE_MASTER = (1 << 0);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_MASTER = (1 << 0);
/**
* @hide
* Flag used when muted by stream volume.
*/
- public static final int PLAYER_MUTE_STREAM_VOLUME = (1 << 1);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_STREAM_VOLUME = (1 << 1);
/**
* @hide
* Flag used when muted by stream mute.
*/
- public static final int PLAYER_MUTE_STREAM_MUTED = (1 << 2);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_STREAM_MUTED = (1 << 2);
/**
* @hide
- * Flag used when playback is restricted by AppOps manager with OP_PLAY_AUDIO.
+ * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
*/
- public static final int PLAYER_MUTE_PLAYBACK_RESTRICTED = (1 << 3);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_APP_OPS = (1 << 3);
/**
* @hide
* Flag used when muted by client volume.
*/
- public static final int PLAYER_MUTE_CLIENT_VOLUME = (1 << 4);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_CLIENT_VOLUME = (1 << 4);
/**
* @hide
* Flag used when muted by volume shaper.
*/
- public static final int PLAYER_MUTE_VOLUME_SHAPER = (1 << 5);
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_VOLUME_SHAPER = (1 << 5);
/** @hide */
@IntDef(
flag = true,
- value = {PLAYER_MUTE_MASTER, PLAYER_MUTE_STREAM_VOLUME, PLAYER_MUTE_STREAM_MUTED,
- PLAYER_MUTE_PLAYBACK_RESTRICTED, PLAYER_MUTE_CLIENT_VOLUME,
- PLAYER_MUTE_VOLUME_SHAPER})
+ value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED,
+ MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerMuteEvent {
}
@@ -303,7 +317,7 @@
mPlayerType = pic.mPlayerType;
mClientUid = uid;
mClientPid = pid;
- mMutedState = PLAYER_MUTE_INVALID;
+ mMutedState = MUTED_BY_UNKNOWN;
mDeviceId = PLAYER_DEVICEID_INVALID;
mPlayerState = PLAYER_STATE_IDLE;
mPlayerAttr = pic.mAttributes;
@@ -352,7 +366,7 @@
anonymCopy.mPlayerAttr = builder.build();
anonymCopy.mDeviceId = in.mDeviceId;
// anonymized data
- anonymCopy.mMutedState = PLAYER_MUTE_INVALID;
+ anonymCopy.mMutedState = MUTED_BY_UNKNOWN;
anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
anonymCopy.mClientUid = PLAYER_UPID_INVALID;
anonymCopy.mClientPid = PLAYER_UPID_INVALID;
@@ -413,9 +427,27 @@
/**
* @hide
- * @return the mute state as a combination of {@link PlayerMuteEvent} flags
+ * Used for determining if the current player is muted.
+ * <br>Note that if this result is true then {@link #getMutedBy} will be > 0.
+ * @return {@code true} if the player associated with this configuration has been muted (by any
+ * given MUTED_BY_* source event) or {@code false} otherwise.
*/
- @PlayerMuteEvent public int getMutedState() {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public boolean isMuted() {
+ return mMutedState != 0 && mMutedState != MUTED_BY_UNKNOWN;
+ }
+
+ /**
+ * @hide
+ * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags.
+ * <br>Note that if the mute state is not set the result will be {@link #MUTED_BY_UNKNOWN}. A
+ * value of 0 represents a player which is not muted.
+ * @return the mute state.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @PlayerMuteEvent public int getMutedBy() {
return mMutedState;
}
@@ -570,14 +602,14 @@
}
private boolean isMuteAffectingActiveState() {
- if (mMutedState == PLAYER_MUTE_INVALID) {
+ if (mMutedState == MUTED_BY_UNKNOWN) {
// mute state is not set, therefore it will not affect the active state
return false;
}
- return (mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0
- || (mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0
- || (mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0;
+ return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
+ || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
+ || (mMutedState & MUTED_BY_APP_OPS) != 0;
}
/**
@@ -694,27 +726,27 @@
"/").append(mClientPid).append(" state:").append(
toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append(
" sessionId:").append(mSessionId).append(" mutedState:");
- if (mMutedState == PLAYER_MUTE_INVALID) {
- apcToString.append("invalid ");
+ if (mMutedState == MUTED_BY_UNKNOWN) {
+ apcToString.append("unknown ");
} else if (mMutedState == 0) {
apcToString.append("none ");
} else {
- if ((mMutedState & PLAYER_MUTE_MASTER) != 0) {
+ if ((mMutedState & MUTED_BY_MASTER) != 0) {
apcToString.append("master ");
}
- if ((mMutedState & PLAYER_MUTE_STREAM_VOLUME) != 0) {
+ if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) {
apcToString.append("streamVolume ");
}
- if ((mMutedState & PLAYER_MUTE_STREAM_MUTED) != 0) {
+ if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
apcToString.append("streamMute ");
}
- if ((mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) {
- apcToString.append("playbackRestricted ");
+ if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
+ apcToString.append("appOps ");
}
- if ((mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0) {
+ if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
apcToString.append("clientVolume ");
}
- if ((mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0) {
+ if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
apcToString.append("volumeShaper ");
}
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index a27fd10..7d84fb2 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -1047,7 +1047,7 @@
}
}
- void notifyRecordingStarted(@Nullable String recordingId) {
+ void notifyRecordingStarted(String recordingId) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
return;
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 3d65effa..2956a0a 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -456,9 +456,10 @@
/**
* Receives started recording's ID.
- * @hide
+ *
+ * @param recordingId The ID of the recording started
*/
- public void onRecordingStarted(@Nullable String recordingId) {
+ public void onRecordingStarted(@NonNull String recordingId) {
}
/**
@@ -914,7 +915,15 @@
/**
* Requests starting of recording
*
- * @hide
+ * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+ * call {@link android.media.tv.TvRecordingClient#startRecording(Uri)} with the provided
+ * {@code programUri}.
+ * A non-null {@code programUri} implies the started recording should be of that specific
+ * program, whereas null {@code programUri} does not impose such a requirement and the
+ * recording can span across multiple TV programs.
+ *
+ * @param programUri The URI for the TV program to record.
+ * @see android.media.tv.TvRecordingClient#startRecording(Uri)
*/
@CallSuper
public void requestStartRecording(@Nullable Uri programUri) {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 76ba69c..1f270d0 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -583,11 +583,9 @@
/**
* Alerts the TV interactive app that a recording has been started with recordingId
*
- * @param recordingId The Id of the recording started
- *
- * @hide
+ * @param recordingId The ID of the recording started
*/
- public void notifyRecordingStarted(@Nullable String recordingId) {
+ public void notifyRecordingStarted(@NonNull String recordingId) {
if (DEBUG) {
Log.d(TAG, "notifyRecordingStarted");
}
@@ -859,10 +857,9 @@
/**
* This is called when {@link TvInteractiveAppService.Session#requestStartRecording(Uri)}
* is called.
+ *
* @param iAppServiceId The ID of the TV interactive app service bound to this view.
* @param programUri The program URI to record
- *
- * @hide
*/
public void onRequestStartRecording(
@NonNull String iAppServiceId,
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
index edb5e37..158e698 100644
--- a/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/MtpClient.java
@@ -158,7 +158,7 @@
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(ACTION_USB_PERMISSION);
- context.registerReceiver(mUsbReceiver, filter);
+ context.registerReceiver(mUsbReceiver, filter, Context.RECEIVER_EXPORTED/*UNAUDITED*/);
}
/**
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 25529bb..d8577c3 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -17,7 +17,11 @@
static_libs: [
"androidx.activity_activity-compose",
"androidx.appcompat_appcompat",
- "androidx.compose.material_material",
+ "androidx.compose.animation_animation-core",
+ "androidx.compose.foundation_foundation",
+ "androidx.compose.material3_material3",
+ "androidx.compose.material_material-icons-core",
+ "androidx.compose.material_material-icons-extended",
"androidx.compose.runtime_runtime",
"androidx.compose.ui_ui",
"androidx.compose.ui_ui-tooling",
@@ -27,6 +31,7 @@
"androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.lifecycle_lifecycle-viewmodel-compose",
"androidx.recyclerview_recyclerview",
+ "kotlinx-coroutines-core",
],
platform_apis: true,
diff --git a/packages/CredentialManager/res/drawable/ic_profile.xml b/packages/CredentialManager/res/drawable/ic_profile.xml
new file mode 100644
index 0000000..ae65940
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_profile.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="46"
+ android:viewportHeight="46"
+ android:width="46dp"
+ android:height="46dp">
+ <path
+ android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
+ android:strokeColor="#202124"
+ android:strokeAlpha="0.13"
+ android:strokeWidth="1" />
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index c6779fa..6178efc 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -22,4 +22,11 @@
<string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
<string name="more_options_title_multiple_options"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for <xliff:g id="createInfoTitle">%2$s</xliff:g></string>
<string name="more_options_title_one_option"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g></string>
+ <string name="more_options_usage_data"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords and <xliff:g id="passkeyssNumber">%2$s</xliff:g> passkeys saved</string>
+ <string name="passkeys">passkeys</string>
+ <string name="passwords">passwords</string>
+ <string name="sign_ins">sign-ins</string>
+ <string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
+ <!-- Spoken content description of an element which will close the sheet when clicked. -->
+ <string name="close_sheet">"Close sheet"</string>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 01348e4..9e9d16f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -33,8 +33,10 @@
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
+import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreatePasskeyUiState
import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
@@ -119,6 +121,10 @@
val providerList = CreateFlowUtils.toProviderList(
// Handle runtime cast error
providerList as List<CreateCredentialProviderData>, context)
+ var hasDefault = false
+ var defaultProvider: ProviderInfo = providerList.first()
+ providerList.forEach{providerInfo ->
+ if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
// TODO: covert from real requestInfo
val requestDisplayInfo = RequestDisplayInfo(
"Elisa Beckett",
@@ -127,8 +133,12 @@
"tribank")
return CreatePasskeyUiState(
providers = providerList,
- currentScreenState = CreateScreenState.PASSKEY_INTRO,
+ if (hasDefault)
+ {CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
requestDisplayInfo,
+ if (hasDefault) {
+ ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+ } else null
)
}
@@ -150,40 +160,29 @@
// TODO: below are prototype functionalities. To be removed for productionization.
private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
return listOf(
- CreateCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
+ CreateCredentialProviderData
+ .Builder("com.google/com.google.CredentialManagerService")
.setSaveEntries(
listOf<Entry>(
newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
- "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 10000),
newEntry("key1", "subkey-2", "elisa.work@google.com",
- "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 11000),
)
)
- .setActionChips(
- listOf<Entry>(
- newEntry("key2", "subkey-1", "Go to Settings", "",
- "20 passwords and 7 passkeys saved"),
- newEntry("key2", "subkey-2", "Switch Account", "",
- "20 passwords and 7 passkeys saved"),
- ),
- )
.setIsDefaultProvider(true)
.build(),
- CreateCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
+ CreateCredentialProviderData
+ .Builder("com.dashlane/com.dashlane.CredentialManagerService")
.setSaveEntries(
listOf<Entry>(
newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
- "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 30000),
newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
- "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 31000),
)
- ).setActionChips(
- listOf<Entry>(
- newEntry("key2", "subkey-3", "Manage Accounts",
- "Manage your accounts in the dashlane app",
- "20 passwords and 7 passkeys saved"),
- ),
- ).build(),
+ )
+ .build(),
)
}
@@ -193,31 +192,30 @@
.setCredentialEntries(
listOf<Entry>(
newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
- "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 10000),
newEntry("key1", "subkey-2", "elisa.work@google.com",
- "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 11000),
)
).setActionChips(
listOf<Entry>(
- newEntry("key2", "subkey-1", "Go to Settings", "",
- "20 passwords and 7 passkeys saved"),
- newEntry("key2", "subkey-2", "Switch Account", "",
- "20 passwords and 7 passkeys saved"),
+ newEntry("key2", "subkey-1", "Go to Settings",
+ 20, 7, 27, 20000),
+ newEntry("key2", "subkey-2", "Switch Account",
+ 20, 7, 27, 21000),
),
).build(),
GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
.setCredentialEntries(
listOf<Entry>(
newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
- "Elisa Backett", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 30000),
newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
- "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 31000),
)
).setActionChips(
listOf<Entry>(
newEntry("key2", "subkey-3", "Manage Accounts",
- "Manage your accounts in the dashlane app",
- "20 passwords and 7 passkeys saved"),
+ 20, 7, 27, 40000),
),
).build(),
)
@@ -226,20 +224,32 @@
private fun newEntry(
key: String,
subkey: String,
- title: String,
- subtitle: String,
- usageData: String
+ providerDisplayName: String,
+ passwordCount: Int,
+ passkeyCount: Int,
+ totalCredentialCount: Int,
+ lastUsedTimeMillis: Long,
): Entry {
val slice = Slice.Builder(
Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
)
- .addText(title, null, listOf(Entry.HINT_TITLE))
- .addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
+ .addText(
+ providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
.addIcon(
Icon.createWithResource(context, R.drawable.ic_passkey),
null,
- listOf(Entry.HINT_ICON))
- .addText(usageData, Slice.SUBTYPE_MESSAGE, listOf(Entry.HINT_SUBTITLE))
+ listOf(Entry.HINT_CREDENTIAL_TYPE_ICON))
+ .addIcon(
+ Icon.createWithResource(context, R.drawable.ic_profile),
+ null,
+ listOf(Entry.HINT_PROFILE_ICON))
+ .addInt(
+ passwordCount, null, listOf(Entry.HINT_PASSWORD_COUNT))
+ .addInt(
+ passkeyCount, null, listOf(Entry.HINT_PASSKEY_COUNT))
+ .addInt(
+ totalCredentialCount, null, listOf(Entry.HINT_TOTAL_CREDENTIAL_COUNT))
+ .addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
.build()
return Entry(
key,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index bf0dba2..3a8e975 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -23,6 +23,7 @@
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.getflow.CredentialOptionInfo
import com.android.credentialmanager.getflow.ProviderInfo
+import com.android.credentialmanager.jetpack.provider.SaveEntryUi
/** Utility functions for converting CredentialManager data structures to or from UI formats. */
class GetFlowUtils {
@@ -59,8 +60,6 @@
// TODO: remove fallbacks
icon = credentialEntryUi.icon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_passkey)!!,
- title = credentialEntryUi.userName.toString(),
- subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
entryKey = it.key,
entrySubkey = it.subkey,
usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
@@ -82,9 +81,7 @@
// TODO: replace to extract from the service data structure when available
icon = context.getDrawable(R.drawable.ic_passkey)!!,
name = it.providerFlattenedComponentName,
- // TODO: get the service display name and icon from the component name.
displayName = it.providerFlattenedComponentName,
- credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
createOptions = toCreationOptionInfoList(it.saveEntries, context),
isDefault = it.isDefaultProvider,
)
@@ -100,13 +97,17 @@
return@map CreateOptionInfo(
// TODO: remove fallbacks
- icon = saveEntryUi.icon?.loadDrawable(context)
- ?: context.getDrawable(R.drawable.ic_passkey)!!,
- title = saveEntryUi.title.toString(),
- subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
entryKey = it.key,
entrySubkey = it.subkey,
- usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData",
+ userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
+ credentialTypeIcon = saveEntryUi.credentialTypeIcon?.loadDrawable(context)
+ ?: context.getDrawable(R.drawable.ic_passkey)!!,
+ profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
+ ?: context.getDrawable(R.drawable.ic_profile)!!,
+ passwordCount = saveEntryUi.passwordCount ?: 0,
+ passkeyCount = saveEntryUi.passkeyCount ?: 0,
+ totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
+ lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
deleted file mode 100644
index cd52197..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a save entry used during the create credential flow.
- *
- * TODO: move to jetpack.
- */
-class SaveEntryUi(
- val title: CharSequence,
- val subTitle: CharSequence?,
- val icon: Icon?,
- val usageData: CharSequence?,
- // TODO: add
-) {
- companion object {
- fun fromSlice(slice: Slice): SaveEntryUi {
- val items = slice.items
-
- var title: String? = null
- var subTitle: String? = null
- var icon: Icon? = null
- var usageData: String? = null
-
- items.forEach {
- if (it.hasHint(Entry.HINT_ICON)) {
- icon = it.icon
- } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) {
- subTitle = it.text.toString()
- } else if (it.hasHint(Entry.HINT_TITLE)) {
- title = it.text.toString()
- } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) {
- usageData = it.text.toString()
- }
- }
- // TODO: fail NPE more elegantly.
- return SaveEntryUi(title!!, subTitle, icon, usageData)
- }
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
new file mode 100644
index 0000000..f1f453d
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.material
+
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.collapse
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.dismiss
+import androidx.compose.ui.semantics.expand
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.Expanded
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.HalfExpanded
+import com.android.credentialmanager.common.material.ModalBottomSheetValue.Hidden
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.launch
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+/**
+ * Possible values of [ModalBottomSheetState].
+ */
+enum class ModalBottomSheetValue {
+ /**
+ * The bottom sheet is not visible.
+ */
+ Hidden,
+
+ /**
+ * The bottom sheet is visible at full height.
+ */
+ Expanded,
+
+ /**
+ * The bottom sheet is partially visible at 50% of the screen height. This state is only
+ * enabled if the height of the bottom sheet is more than 50% of the screen height.
+ */
+ HalfExpanded
+}
+
+/**
+ * State of the [ModalBottomSheetLayout] composable.
+ *
+ * @param initialValue The initial value of the state. <b>Must not be set to
+ * [ModalBottomSheetValue.HalfExpanded] if [isSkipHalfExpanded] is set to true.</b>
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param isSkipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should
+ * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the
+ * [Hidden] state when hiding the sheet, either programmatically or by user interaction.
+ * <b>Must not be set to true if the [initialValue] is [ModalBottomSheetValue.HalfExpanded].</b>
+ * If supplied with [ModalBottomSheetValue.HalfExpanded] for the [initialValue], an
+ * [IllegalArgumentException] will be thrown.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+class ModalBottomSheetState(
+ initialValue: ModalBottomSheetValue,
+ animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+ internal val isSkipHalfExpanded: Boolean,
+ confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+) : SwipeableState<ModalBottomSheetValue>(
+ initialValue = initialValue,
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange
+) {
+ /**
+ * Whether the bottom sheet is visible.
+ */
+ val isVisible: Boolean
+ get() = currentValue != Hidden
+
+ internal val hasHalfExpandedState: Boolean
+ get() = anchors.values.contains(HalfExpanded)
+
+ constructor(
+ initialValue: ModalBottomSheetValue,
+ animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+ confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+ ) : this(initialValue, animationSpec, isSkipHalfExpanded = false, confirmStateChange)
+
+ init {
+ if (isSkipHalfExpanded) {
+ require(initialValue != HalfExpanded) {
+ "The initial value must not be set to HalfExpanded if skipHalfExpanded is set to" +
+ " true."
+ }
+ }
+ }
+
+ /**
+ * Show the bottom sheet with animation and suspend until it's shown. If the sheet is taller
+ * than 50% of the parent's height, the bottom sheet will be half expanded. Otherwise it will be
+ * fully expanded.
+ *
+ * @throws [CancellationException] if the animation is interrupted
+ */
+ suspend fun show() {
+ val targetValue = when {
+ hasHalfExpandedState -> HalfExpanded
+ else -> Expanded
+ }
+ animateTo(targetValue = targetValue)
+ }
+
+ /**
+ * Half expand the bottom sheet if half expand is enabled with animation and suspend until it
+ * animation is complete or cancelled
+ *
+ * @throws [CancellationException] if the animation is interrupted
+ */
+ internal suspend fun halfExpand() {
+ if (!hasHalfExpandedState) {
+ return
+ }
+ animateTo(HalfExpanded)
+ }
+
+ /**
+ * Fully expand the bottom sheet with animation and suspend until it if fully expanded or
+ * animation has been cancelled.
+ * *
+ * @throws [CancellationException] if the animation is interrupted
+ */
+ internal suspend fun expand() = animateTo(Expanded)
+
+ /**
+ * Hide the bottom sheet with animation and suspend until it if fully hidden or animation has
+ * been cancelled.
+ *
+ * @throws [CancellationException] if the animation is interrupted
+ */
+ suspend fun hide() = animateTo(Hidden)
+
+ internal val nestedScrollConnection = this.PreUpPostDownNestedScrollConnection
+
+ companion object {
+ /**
+ * The default [Saver] implementation for [ModalBottomSheetState].
+ */
+ fun Saver(
+ animationSpec: AnimationSpec<Float>,
+ skipHalfExpanded: Boolean,
+ confirmStateChange: (ModalBottomSheetValue) -> Boolean
+ ): Saver<ModalBottomSheetState, *> = Saver(
+ save = { it.currentValue },
+ restore = {
+ ModalBottomSheetState(
+ initialValue = it,
+ animationSpec = animationSpec,
+ isSkipHalfExpanded = skipHalfExpanded,
+ confirmStateChange = confirmStateChange
+ )
+ }
+ )
+
+ /**
+ * The default [Saver] implementation for [ModalBottomSheetState].
+ */
+ @Deprecated(
+ message = "Please specify the skipHalfExpanded parameter",
+ replaceWith = ReplaceWith(
+ "ModalBottomSheetState.Saver(" +
+ "animationSpec = animationSpec," +
+ "skipHalfExpanded = ," +
+ "confirmStateChange = confirmStateChange" +
+ ")"
+ )
+ )
+ fun Saver(
+ animationSpec: AnimationSpec<Float>,
+ confirmStateChange: (ModalBottomSheetValue) -> Boolean
+ ): Saver<ModalBottomSheetState, *> = Saver(
+ animationSpec = animationSpec,
+ skipHalfExpanded = false,
+ confirmStateChange = confirmStateChange
+ )
+ }
+}
+
+/**
+ * Create a [ModalBottomSheetState] and [remember] it.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param skipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should
+ * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the
+ * [Hidden] state when hiding the sheet, either programmatically or by user interaction.
+ * <b>Must not be set to true if the [initialValue] is [ModalBottomSheetValue.HalfExpanded].</b>
+ * If supplied with [ModalBottomSheetValue.HalfExpanded] for the [initialValue], an
+ * [IllegalArgumentException] will be thrown.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun rememberModalBottomSheetState(
+ initialValue: ModalBottomSheetValue,
+ animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+ skipHalfExpanded: Boolean,
+ confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+): ModalBottomSheetState {
+ return rememberSaveable(
+ initialValue, animationSpec, skipHalfExpanded, confirmStateChange,
+ saver = ModalBottomSheetState.Saver(
+ animationSpec = animationSpec,
+ skipHalfExpanded = skipHalfExpanded,
+ confirmStateChange = confirmStateChange
+ )
+ ) {
+ ModalBottomSheetState(
+ initialValue = initialValue,
+ animationSpec = animationSpec,
+ isSkipHalfExpanded = skipHalfExpanded,
+ confirmStateChange = confirmStateChange
+ )
+ }
+}
+
+/**
+ * Create a [ModalBottomSheetState] and [remember] it.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun rememberModalBottomSheetState(
+ initialValue: ModalBottomSheetValue,
+ animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec,
+ confirmStateChange: (ModalBottomSheetValue) -> Boolean = { true }
+): ModalBottomSheetState = rememberModalBottomSheetState(
+ initialValue = initialValue,
+ animationSpec = animationSpec,
+ skipHalfExpanded = false,
+ confirmStateChange = confirmStateChange
+)
+
+/**
+ * <a href="https://material.io/components/sheets-bottom#modal-bottom-sheet" class="external" target="_blank">Material Design modal bottom sheet</a>.
+ *
+ * Modal bottom sheets present a set of choices while blocking interaction with the rest of the
+ * screen. They are an alternative to inline menus and simple dialogs, providing
+ * additional room for content, iconography, and actions.
+ *
+ * 
+ *
+ * A simple example of a modal bottom sheet looks like this:
+ *
+ * @sample androidx.compose.material.samples.ModalBottomSheetSample
+ *
+ * @param sheetContent The content of the bottom sheet.
+ * @param modifier Optional [Modifier] for the entire component.
+ * @param sheetState The state of the bottom sheet.
+ * @param sheetShape The shape of the bottom sheet.
+ * @param sheetElevation The elevation of the bottom sheet.
+ * @param sheetBackgroundColor The background color of the bottom sheet.
+ * @param sheetContentColor The preferred content color provided by the bottom sheet to its
+ * children. Defaults to the matching content color for [sheetBackgroundColor], or if that is not
+ * a color from the theme, this will keep the same content color set above the bottom sheet.
+ * @param scrimColor The color of the scrim that is applied to the rest of the screen when the
+ * bottom sheet is visible. If the color passed is [Color.Unspecified], then a scrim will no
+ * longer be applied and the bottom sheet will not block interaction with the rest of the screen
+ * when visible.
+ * @param content The content of rest of the screen.
+ */
+@Composable
+fun ModalBottomSheetLayout(
+ sheetContent: @Composable ColumnScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ sheetState: ModalBottomSheetState =
+ rememberModalBottomSheetState(Hidden),
+ sheetShape: Shape = MaterialTheme.shapes.large,
+ sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
+ sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
+ scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
+ content: @Composable () -> Unit
+) {
+ val scope = rememberCoroutineScope()
+ BoxWithConstraints(modifier) {
+ val fullHeight = constraints.maxHeight.toFloat()
+ val sheetHeightState = remember { mutableStateOf<Float?>(null) }
+ Box(Modifier.fillMaxSize()) {
+ content()
+ Scrim(
+ color = scrimColor,
+ onDismiss = {
+ if (sheetState.confirmStateChange(Hidden)) {
+ scope.launch { sheetState.hide() }
+ }
+ },
+ visible = sheetState.targetValue != Hidden
+ )
+ }
+ Surface(
+ Modifier
+ .fillMaxWidth()
+ .nestedScroll(sheetState.nestedScrollConnection)
+ .offset {
+ val y = if (sheetState.anchors.isEmpty()) {
+ // if we don't know our anchors yet, render the sheet as hidden
+ fullHeight.roundToInt()
+ } else {
+ // if we do know our anchors, respect them
+ sheetState.offset.value.roundToInt()
+ }
+ IntOffset(0, y)
+ }
+ .bottomSheetSwipeable(sheetState, fullHeight, sheetHeightState)
+ .onGloballyPositioned {
+ sheetHeightState.value = it.size.height.toFloat()
+ }
+ .semantics {
+ if (sheetState.isVisible) {
+ dismiss {
+ if (sheetState.confirmStateChange(Hidden)) {
+ scope.launch { sheetState.hide() }
+ }
+ true
+ }
+ if (sheetState.currentValue == HalfExpanded) {
+ expand {
+ if (sheetState.confirmStateChange(Expanded)) {
+ scope.launch { sheetState.expand() }
+ }
+ true
+ }
+ } else if (sheetState.hasHalfExpandedState) {
+ collapse {
+ if (sheetState.confirmStateChange(HalfExpanded)) {
+ scope.launch { sheetState.halfExpand() }
+ }
+ true
+ }
+ }
+ }
+ },
+ shape = sheetShape,
+ shadowElevation = sheetElevation,
+ color = sheetBackgroundColor,
+ contentColor = sheetContentColor
+ ) {
+ Column(content = sheetContent)
+ }
+ }
+}
+
+@Suppress("ModifierInspectorInfo")
+private fun Modifier.bottomSheetSwipeable(
+ sheetState: ModalBottomSheetState,
+ fullHeight: Float,
+ sheetHeightState: State<Float?>
+): Modifier {
+ val sheetHeight = sheetHeightState.value
+ val modifier = if (sheetHeight != null) {
+ val anchors = if (sheetHeight < fullHeight / 2 || sheetState.isSkipHalfExpanded) {
+ mapOf(
+ fullHeight to Hidden,
+ fullHeight - sheetHeight to Expanded
+ )
+ } else {
+ mapOf(
+ fullHeight to Hidden,
+ fullHeight / 2 to HalfExpanded,
+ max(0f, fullHeight - sheetHeight) to Expanded
+ )
+ }
+ Modifier.swipeable(
+ state = sheetState,
+ anchors = anchors,
+ orientation = Orientation.Vertical,
+ enabled = sheetState.currentValue != Hidden,
+ resistance = null
+ )
+ } else {
+ Modifier
+ }
+
+ return this.then(modifier)
+}
+
+@Composable
+private fun Scrim(
+ color: Color,
+ onDismiss: () -> Unit,
+ visible: Boolean
+) {
+ if (color.isSpecified) {
+ val alpha by animateFloatAsState(
+ targetValue = if (visible) 1f else 0f,
+ animationSpec = TweenSpec()
+ )
+ LocalConfiguration.current
+ val resources = LocalContext.current.resources
+ val closeSheet = resources.getString(R.string.close_sheet)
+ val dismissModifier = if (visible) {
+ Modifier
+ .pointerInput(onDismiss) { detectTapGestures { onDismiss() } }
+ .semantics(mergeDescendants = true) {
+ contentDescription = closeSheet
+ onClick { onDismiss(); true }
+ }
+ } else {
+ Modifier
+ }
+
+ Canvas(
+ Modifier
+ .fillMaxSize()
+ .then(dismissModifier)
+ ) {
+ drawRect(color = color, alpha = alpha)
+ }
+ }
+}
+
+/**
+ * Contains useful Defaults for [ModalBottomSheetLayout].
+ */
+object ModalBottomSheetDefaults {
+
+ /**
+ * The default elevation used by [ModalBottomSheetLayout].
+ */
+ val Elevation = 16.dp
+
+ /**
+ * The default scrim color used by [ModalBottomSheetLayout].
+ */
+ val scrimColor: Color
+ @Composable
+ get() = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f)
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
new file mode 100644
index 0000000..3e2de83
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/Swipeable.kt
@@ -0,0 +1,875 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.material
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.DraggableState
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
+import com.android.credentialmanager.common.material.SwipeableDefaults.AnimationSpec
+import com.android.credentialmanager.common.material.SwipeableDefaults.StandardResistanceFactor
+import com.android.credentialmanager.common.material.SwipeableDefaults.VelocityThreshold
+import com.android.credentialmanager.common.material.SwipeableDefaults.resistanceConfig
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.launch
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.sign
+import kotlin.math.sin
+
+/**
+ * State of the [swipeable] modifier.
+ *
+ * This contains necessary information about any ongoing swipe or animation and provides methods
+ * to change the state either immediately or by starting an animation. To create and remember a
+ * [SwipeableState] with the default animation clock, use [rememberSwipeableState].
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Stable
+open class SwipeableState<T>(
+ initialValue: T,
+ internal val animationSpec: AnimationSpec<Float> = AnimationSpec,
+ internal val confirmStateChange: (newValue: T) -> Boolean = { true }
+) {
+ /**
+ * The current value of the state.
+ *
+ * If no swipe or animation is in progress, this corresponds to the anchor at which the
+ * [swipeable] is currently settled. If a swipe or animation is in progress, this corresponds
+ * the last anchor at which the [swipeable] was settled before the swipe or animation started.
+ */
+ var currentValue: T by mutableStateOf(initialValue)
+ private set
+
+ /**
+ * Whether the state is currently animating.
+ */
+ var isAnimationRunning: Boolean by mutableStateOf(false)
+ private set
+
+ /**
+ * The current position (in pixels) of the [swipeable].
+ *
+ * You should use this state to offset your content accordingly. The recommended way is to
+ * use `Modifier.offsetPx`. This includes the resistance by default, if resistance is enabled.
+ */
+ val offset: State<Float> get() = offsetState
+
+ /**
+ * The amount by which the [swipeable] has been swiped past its bounds.
+ */
+ val overflow: State<Float> get() = overflowState
+
+ // Use `Float.NaN` as a placeholder while the state is uninitialised.
+ private val offsetState = mutableStateOf(0f)
+ private val overflowState = mutableStateOf(0f)
+
+ // the source of truth for the "real"(non ui) position
+ // basically position in bounds + overflow
+ private val absoluteOffset = mutableStateOf(0f)
+
+ // current animation target, if animating, otherwise null
+ private val animationTarget = mutableStateOf<Float?>(null)
+
+ internal var anchors by mutableStateOf(emptyMap<Float, T>())
+
+ private val latestNonEmptyAnchorsFlow: Flow<Map<Float, T>> =
+ snapshotFlow { anchors }
+ .filter { it.isNotEmpty() }
+ .take(1)
+
+ internal var minBound = Float.NEGATIVE_INFINITY
+ internal var maxBound = Float.POSITIVE_INFINITY
+
+ internal fun ensureInit(newAnchors: Map<Float, T>) {
+ if (anchors.isEmpty()) {
+ // need to do initial synchronization synchronously :(
+ val initialOffset = newAnchors.getOffset(currentValue)
+ requireNotNull(initialOffset) {
+ "The initial value must have an associated anchor."
+ }
+ offsetState.value = initialOffset
+ absoluteOffset.value = initialOffset
+ }
+ }
+
+ internal suspend fun processNewAnchors(
+ oldAnchors: Map<Float, T>,
+ newAnchors: Map<Float, T>
+ ) {
+ if (oldAnchors.isEmpty()) {
+ // If this is the first time that we receive anchors, then we need to initialise
+ // the state so we snap to the offset associated to the initial value.
+ minBound = newAnchors.keys.minOrNull()!!
+ maxBound = newAnchors.keys.maxOrNull()!!
+ val initialOffset = newAnchors.getOffset(currentValue)
+ requireNotNull(initialOffset) {
+ "The initial value must have an associated anchor."
+ }
+ snapInternalToOffset(initialOffset)
+ } else if (newAnchors != oldAnchors) {
+ // If we have received new anchors, then the offset of the current value might
+ // have changed, so we need to animate to the new offset. If the current value
+ // has been removed from the anchors then we animate to the closest anchor
+ // instead. Note that this stops any ongoing animation.
+ minBound = Float.NEGATIVE_INFINITY
+ maxBound = Float.POSITIVE_INFINITY
+ val animationTargetValue = animationTarget.value
+ // if we're in the animation already, let's find it a new home
+ val targetOffset = if (animationTargetValue != null) {
+ // first, try to map old state to the new state
+ val oldState = oldAnchors[animationTargetValue]
+ val newState = newAnchors.getOffset(oldState)
+ // return new state if exists, or find the closes one among new anchors
+ newState ?: newAnchors.keys.minByOrNull { abs(it - animationTargetValue) }!!
+ } else {
+ // we're not animating, proceed by finding the new anchors for an old value
+ val actualOldValue = oldAnchors[offset.value]
+ val value = if (actualOldValue == currentValue) currentValue else actualOldValue
+ newAnchors.getOffset(value) ?: newAnchors
+ .keys.minByOrNull { abs(it - offset.value) }!!
+ }
+ try {
+ animateInternalToOffset(targetOffset, animationSpec)
+ } catch (c: CancellationException) {
+ // If the animation was interrupted for any reason, snap as a last resort.
+ snapInternalToOffset(targetOffset)
+ } finally {
+ currentValue = newAnchors.getValue(targetOffset)
+ minBound = newAnchors.keys.minOrNull()!!
+ maxBound = newAnchors.keys.maxOrNull()!!
+ }
+ }
+ }
+
+ internal var thresholds: (Float, Float) -> Float by mutableStateOf({ _, _ -> 0f })
+
+ internal var velocityThreshold by mutableStateOf(0f)
+
+ internal var resistance: ResistanceConfig? by mutableStateOf(null)
+
+ internal val draggableState = DraggableState {
+ val newAbsolute = absoluteOffset.value + it
+ val clamped = newAbsolute.coerceIn(minBound, maxBound)
+ val overflow = newAbsolute - clamped
+ val resistanceDelta = resistance?.computeResistance(overflow) ?: 0f
+ offsetState.value = clamped + resistanceDelta
+ overflowState.value = overflow
+ absoluteOffset.value = newAbsolute
+ }
+
+ private suspend fun snapInternalToOffset(target: Float) {
+ draggableState.drag {
+ dragBy(target - absoluteOffset.value)
+ }
+ }
+
+ private suspend fun animateInternalToOffset(target: Float, spec: AnimationSpec<Float>) {
+ draggableState.drag {
+ var prevValue = absoluteOffset.value
+ animationTarget.value = target
+ isAnimationRunning = true
+ try {
+ Animatable(prevValue).animateTo(target, spec) {
+ dragBy(this.value - prevValue)
+ prevValue = this.value
+ }
+ } finally {
+ animationTarget.value = null
+ isAnimationRunning = false
+ }
+ }
+ }
+
+ /**
+ * The target value of the state.
+ *
+ * If a swipe is in progress, this is the value that the [swipeable] would animate to if the
+ * swipe finished. If an animation is running, this is the target value of that animation.
+ * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
+ */
+ val targetValue: T
+ get() {
+ // TODO(calintat): Track current velocity (b/149549482) and use that here.
+ val target = animationTarget.value ?: computeTarget(
+ offset = offset.value,
+ lastValue = anchors.getOffset(currentValue) ?: offset.value,
+ anchors = anchors.keys,
+ thresholds = thresholds,
+ velocity = 0f,
+ velocityThreshold = Float.POSITIVE_INFINITY
+ )
+ return anchors[target] ?: currentValue
+ }
+
+ /**
+ * Information about the ongoing swipe or animation, if any. See [SwipeProgress] for details.
+ *
+ * If no swipe or animation is in progress, this returns `SwipeProgress(value, value, 1f)`.
+ */
+ val progress: SwipeProgress<T>
+ get() {
+ val bounds = findBounds(offset.value, anchors.keys)
+ val from: T
+ val to: T
+ val fraction: Float
+ when (bounds.size) {
+ 0 -> {
+ from = currentValue
+ to = currentValue
+ fraction = 1f
+ }
+ 1 -> {
+ from = anchors.getValue(bounds[0])
+ to = anchors.getValue(bounds[0])
+ fraction = 1f
+ }
+ else -> {
+ val (a, b) =
+ if (direction > 0f) {
+ bounds[0] to bounds[1]
+ } else {
+ bounds[1] to bounds[0]
+ }
+ from = anchors.getValue(a)
+ to = anchors.getValue(b)
+ fraction = (offset.value - a) / (b - a)
+ }
+ }
+ return SwipeProgress(from, to, fraction)
+ }
+
+ /**
+ * The direction in which the [swipeable] is moving, relative to the current [currentValue].
+ *
+ * This will be either 1f if it is is moving from left to right or top to bottom, -1f if it is
+ * moving from right to left or bottom to top, or 0f if no swipe or animation is in progress.
+ */
+ val direction: Float
+ get() = anchors.getOffset(currentValue)?.let { sign(offset.value - it) } ?: 0f
+
+ /**
+ * Set the state without any animation and suspend until it's set
+ *
+ * @param targetValue The new target value to set [currentValue] to.
+ */
+ suspend fun snapTo(targetValue: T) {
+ latestNonEmptyAnchorsFlow.collect { anchors ->
+ val targetOffset = anchors.getOffset(targetValue)
+ requireNotNull(targetOffset) {
+ "The target value must have an associated anchor."
+ }
+ snapInternalToOffset(targetOffset)
+ currentValue = targetValue
+ }
+ }
+
+ /**
+ * Set the state to the target value by starting an animation.
+ *
+ * @param targetValue The new value to animate to.
+ * @param anim The animation that will be used to animate to the new value.
+ */
+ suspend fun animateTo(targetValue: T, anim: AnimationSpec<Float> = animationSpec) {
+ latestNonEmptyAnchorsFlow.collect { anchors ->
+ try {
+ val targetOffset = anchors.getOffset(targetValue)
+ requireNotNull(targetOffset) {
+ "The target value must have an associated anchor."
+ }
+ animateInternalToOffset(targetOffset, anim)
+ } finally {
+ val endOffset = absoluteOffset.value
+ val endValue = anchors
+ // fighting rounding error once again, anchor should be as close as 0.5 pixels
+ .filterKeys { anchorOffset -> abs(anchorOffset - endOffset) < 0.5f }
+ .values.firstOrNull() ?: currentValue
+ currentValue = endValue
+ }
+ }
+ }
+
+ /**
+ * Perform fling with settling to one of the anchors which is determined by the given
+ * [velocity]. Fling with settling [swipeable] will always consume all the velocity provided
+ * since it will settle at the anchor.
+ *
+ * In general cases, [swipeable] flings by itself when being swiped. This method is to be
+ * used for nested scroll logic that wraps the [swipeable]. In nested scroll developer may
+ * want to trigger settling fling when the child scroll container reaches the bound.
+ *
+ * @param velocity velocity to fling and settle with
+ *
+ * @return the reason fling ended
+ */
+ suspend fun performFling(velocity: Float) {
+ latestNonEmptyAnchorsFlow.collect { anchors ->
+ val lastAnchor = anchors.getOffset(currentValue)!!
+ val targetValue = computeTarget(
+ offset = offset.value,
+ lastValue = lastAnchor,
+ anchors = anchors.keys,
+ thresholds = thresholds,
+ velocity = velocity,
+ velocityThreshold = velocityThreshold
+ )
+ val targetState = anchors[targetValue]
+ if (targetState != null && confirmStateChange(targetState)) animateTo(targetState)
+ // If the user vetoed the state change, rollback to the previous state.
+ else animateInternalToOffset(lastAnchor, animationSpec)
+ }
+ }
+
+ /**
+ * Force [swipeable] to consume drag delta provided from outside of the regular [swipeable]
+ * gesture flow.
+ *
+ * Note: This method performs generic drag and it won't settle to any particular anchor, *
+ * leaving swipeable in between anchors. When done dragging, [performFling] must be
+ * called as well to ensure swipeable will settle at the anchor.
+ *
+ * In general cases, [swipeable] drags by itself when being swiped. This method is to be
+ * used for nested scroll logic that wraps the [swipeable]. In nested scroll developer may
+ * want to force drag when the child scroll container reaches the bound.
+ *
+ * @param delta delta in pixels to drag by
+ *
+ * @return the amount of [delta] consumed
+ */
+ fun performDrag(delta: Float): Float {
+ val potentiallyConsumed = absoluteOffset.value + delta
+ val clamped = potentiallyConsumed.coerceIn(minBound, maxBound)
+ val deltaToConsume = clamped - absoluteOffset.value
+ if (abs(deltaToConsume) > 0) {
+ draggableState.dispatchRawDelta(deltaToConsume)
+ }
+ return deltaToConsume
+ }
+
+ companion object {
+ /**
+ * The default [Saver] implementation for [SwipeableState].
+ */
+ fun <T : Any> Saver(
+ animationSpec: AnimationSpec<Float>,
+ confirmStateChange: (T) -> Boolean
+ ) = Saver<SwipeableState<T>, T>(
+ save = { it.currentValue },
+ restore = { SwipeableState(it, animationSpec, confirmStateChange) }
+ )
+ }
+}
+
+/**
+ * Collects information about the ongoing swipe or animation in [swipeable].
+ *
+ * To access this information, use [SwipeableState.progress].
+ *
+ * @param from The state corresponding to the anchor we are moving away from.
+ * @param to The state corresponding to the anchor we are moving towards.
+ * @param fraction The fraction that the current position represents between [from] and [to].
+ * Must be between `0` and `1`.
+ */
+@Immutable
+class SwipeProgress<T>(
+ val from: T,
+ val to: T,
+ /*@FloatRange(from = 0.0, to = 1.0)*/
+ val fraction: Float
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is SwipeProgress<*>) return false
+
+ if (from != other.from) return false
+ if (to != other.to) return false
+ if (fraction != other.fraction) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = from?.hashCode() ?: 0
+ result = 31 * result + (to?.hashCode() ?: 0)
+ result = 31 * result + fraction.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "SwipeProgress(from=$from, to=$to, fraction=$fraction)"
+ }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] with the default animation clock.
+ *
+ * @param initialValue The initial value of the state.
+ * @param animationSpec The default animation that will be used to animate to a new state.
+ * @param confirmStateChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+fun <T : Any> rememberSwipeableState(
+ initialValue: T,
+ animationSpec: AnimationSpec<Float> = AnimationSpec,
+ confirmStateChange: (newValue: T) -> Boolean = { true }
+): SwipeableState<T> {
+ return rememberSaveable(
+ saver = SwipeableState.Saver(
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange
+ )
+ ) {
+ SwipeableState(
+ initialValue = initialValue,
+ animationSpec = animationSpec,
+ confirmStateChange = confirmStateChange
+ )
+ }
+}
+
+/**
+ * Create and [remember] a [SwipeableState] which is kept in sync with another state, i.e.:
+ * 1. Whenever the [value] changes, the [SwipeableState] will be animated to that new value.
+ * 2. Whenever the value of the [SwipeableState] changes (e.g. after a swipe), the owner of the
+ * [value] will be notified to update their state to the new value of the [SwipeableState] by
+ * invoking [onValueChange]. If the owner does not update their state to the provided value for
+ * some reason, then the [SwipeableState] will perform a rollback to the previous, correct value.
+ */
+@Composable
+internal fun <T : Any> rememberSwipeableStateFor(
+ value: T,
+ onValueChange: (T) -> Unit,
+ animationSpec: AnimationSpec<Float> = AnimationSpec
+): SwipeableState<T> {
+ val swipeableState = remember {
+ SwipeableState(
+ initialValue = value,
+ animationSpec = animationSpec,
+ confirmStateChange = { true }
+ )
+ }
+ val forceAnimationCheck = remember { mutableStateOf(false) }
+ LaunchedEffect(value, forceAnimationCheck.value) {
+ if (value != swipeableState.currentValue) {
+ swipeableState.animateTo(value)
+ }
+ }
+ DisposableEffect(swipeableState.currentValue) {
+ if (value != swipeableState.currentValue) {
+ onValueChange(swipeableState.currentValue)
+ forceAnimationCheck.value = !forceAnimationCheck.value
+ }
+ onDispose { }
+ }
+ return swipeableState
+}
+
+/**
+ * Enable swipe gestures between a set of predefined states.
+ *
+ * To use this, you must provide a map of anchors (in pixels) to states (of type [T]).
+ * Note that this map cannot be empty and cannot have two anchors mapped to the same state.
+ *
+ * When a swipe is detected, the offset of the [SwipeableState] will be updated with the swipe
+ * delta. You should use this offset to move your content accordingly (see `Modifier.offsetPx`).
+ * When the swipe ends, the offset will be animated to one of the anchors and when that anchor is
+ * reached, the value of the [SwipeableState] will also be updated to the state corresponding to
+ * the new anchor. The target anchor is calculated based on the provided positional [thresholds].
+ *
+ * Swiping is constrained between the minimum and maximum anchors. If the user attempts to swipe
+ * past these bounds, a resistance effect will be applied by default. The amount of resistance at
+ * each edge is specified by the [resistance] config. To disable all resistance, set it to `null`.
+ *
+ * For an example of a [swipeable] with three states, see:
+ *
+ * @sample androidx.compose.material.samples.SwipeableSample
+ *
+ * @param T The type of the state.
+ * @param state The state of the [swipeable].
+ * @param anchors Pairs of anchors and states, used to map anchors to states and vice versa.
+ * @param thresholds Specifies where the thresholds between the states are. The thresholds will be
+ * used to determine which state to animate to when swiping stops. This is represented as a lambda
+ * that takes two states and returns the threshold between them in the form of a [ThresholdConfig].
+ * Note that the order of the states corresponds to the swipe direction.
+ * @param orientation The orientation in which the [swipeable] can be swiped.
+ * @param enabled Whether this [swipeable] is enabled and should react to the user's input.
+ * @param reverseDirection Whether to reverse the direction of the swipe, so a top to bottom
+ * swipe will behave like bottom to top, and a left to right swipe will behave like right to left.
+ * @param interactionSource Optional [MutableInteractionSource] that will passed on to
+ * the internal [Modifier.draggable].
+ * @param resistance Controls how much resistance will be applied when swiping past the bounds.
+ * @param velocityThreshold The threshold (in dp per second) that the end velocity has to exceed
+ * in order to animate to the next state, even if the positional [thresholds] have not been reached.
+ */
+fun <T> Modifier.swipeable(
+ state: SwipeableState<T>,
+ anchors: Map<Float, T>,
+ orientation: Orientation,
+ enabled: Boolean = true,
+ reverseDirection: Boolean = false,
+ interactionSource: MutableInteractionSource? = null,
+ thresholds: (from: T, to: T) -> ThresholdConfig = { _, _ -> FixedThreshold(56.dp) },
+ resistance: ResistanceConfig? = resistanceConfig(anchors.keys),
+ velocityThreshold: Dp = VelocityThreshold
+) = composed(
+ inspectorInfo = debugInspectorInfo {
+ name = "swipeable"
+ properties["state"] = state
+ properties["anchors"] = anchors
+ properties["orientation"] = orientation
+ properties["enabled"] = enabled
+ properties["reverseDirection"] = reverseDirection
+ properties["interactionSource"] = interactionSource
+ properties["thresholds"] = thresholds
+ properties["resistance"] = resistance
+ properties["velocityThreshold"] = velocityThreshold
+ }
+) {
+ require(anchors.isNotEmpty()) {
+ "You must have at least one anchor."
+ }
+ require(anchors.values.distinct().count() == anchors.size) {
+ "You cannot have two anchors mapped to the same state."
+ }
+ val density = LocalDensity.current
+ state.ensureInit(anchors)
+ LaunchedEffect(anchors, state) {
+ val oldAnchors = state.anchors
+ state.anchors = anchors
+ state.resistance = resistance
+ state.thresholds = { a, b ->
+ val from = anchors.getValue(a)
+ val to = anchors.getValue(b)
+ with(thresholds(from, to)) { density.computeThreshold(a, b) }
+ }
+ with(density) {
+ state.velocityThreshold = velocityThreshold.toPx()
+ }
+ state.processNewAnchors(oldAnchors, anchors)
+ }
+
+ Modifier.draggable(
+ orientation = orientation,
+ enabled = enabled,
+ reverseDirection = reverseDirection,
+ interactionSource = interactionSource,
+ startDragImmediately = state.isAnimationRunning,
+ onDragStopped = { velocity -> launch { state.performFling(velocity) } },
+ state = state.draggableState
+ )
+}
+
+/**
+ * Interface to compute a threshold between two anchors/states in a [swipeable].
+ *
+ * To define a [ThresholdConfig], consider using [FixedThreshold] and [FractionalThreshold].
+ */
+@Stable
+interface ThresholdConfig {
+ /**
+ * Compute the value of the threshold (in pixels), once the values of the anchors are known.
+ */
+ fun Density.computeThreshold(fromValue: Float, toValue: Float): Float
+}
+
+/**
+ * A fixed threshold will be at an [offset] away from the first anchor.
+ *
+ * @param offset The offset (in dp) that the threshold will be at.
+ */
+@Immutable
+data class FixedThreshold(private val offset: Dp) : ThresholdConfig {
+ override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+ return fromValue + offset.toPx() * sign(toValue - fromValue)
+ }
+}
+
+/**
+ * A fractional threshold will be at a [fraction] of the way between the two anchors.
+ *
+ * @param fraction The fraction (between 0 and 1) that the threshold will be at.
+ */
+@Immutable
+data class FractionalThreshold(
+ /*@FloatRange(from = 0.0, to = 1.0)*/
+ private val fraction: Float
+) : ThresholdConfig {
+ override fun Density.computeThreshold(fromValue: Float, toValue: Float): Float {
+ return lerp(fromValue, toValue, fraction)
+ }
+}
+
+/**
+ * Specifies how resistance is calculated in [swipeable].
+ *
+ * There are two things needed to calculate resistance: the resistance basis determines how much
+ * overflow will be consumed to achieve maximum resistance, and the resistance factor determines
+ * the amount of resistance (the larger the resistance factor, the stronger the resistance).
+ *
+ * The resistance basis is usually either the size of the component which [swipeable] is applied
+ * to, or the distance between the minimum and maximum anchors. For a constructor in which the
+ * resistance basis defaults to the latter, consider using [resistanceConfig].
+ *
+ * You may specify different resistance factors for each bound. Consider using one of the default
+ * resistance factors in [SwipeableDefaults]: `StandardResistanceFactor` to convey that the user
+ * has run out of things to see, and `StiffResistanceFactor` to convey that the user cannot swipe
+ * this right now. Also, you can set either factor to 0 to disable resistance at that bound.
+ *
+ * @param basis Specifies the maximum amount of overflow that will be consumed. Must be positive.
+ * @param factorAtMin The factor by which to scale the resistance at the minimum bound.
+ * Must not be negative.
+ * @param factorAtMax The factor by which to scale the resistance at the maximum bound.
+ * Must not be negative.
+ */
+@Immutable
+class ResistanceConfig(
+ /*@FloatRange(from = 0.0, fromInclusive = false)*/
+ val basis: Float,
+ /*@FloatRange(from = 0.0)*/
+ val factorAtMin: Float = StandardResistanceFactor,
+ /*@FloatRange(from = 0.0)*/
+ val factorAtMax: Float = StandardResistanceFactor
+) {
+ fun computeResistance(overflow: Float): Float {
+ val factor = if (overflow < 0) factorAtMin else factorAtMax
+ if (factor == 0f) return 0f
+ val progress = (overflow / basis).coerceIn(-1f, 1f)
+ return basis / factor * sin(progress * PI.toFloat() / 2)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is ResistanceConfig) return false
+
+ if (basis != other.basis) return false
+ if (factorAtMin != other.factorAtMin) return false
+ if (factorAtMax != other.factorAtMax) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = basis.hashCode()
+ result = 31 * result + factorAtMin.hashCode()
+ result = 31 * result + factorAtMax.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "ResistanceConfig(basis=$basis, factorAtMin=$factorAtMin, factorAtMax=$factorAtMax)"
+ }
+}
+
+/**
+ * Given an offset x and a set of anchors, return a list of anchors:
+ * 1. [ ] if the set of anchors is empty,
+ * 2. [ x' ] if x is equal to one of the anchors, accounting for a small rounding error, where x'
+ * is x rounded to the exact value of the matching anchor,
+ * 3. [ min ] if min is the minimum anchor and x < min,
+ * 4. [ max ] if max is the maximum anchor and x > max, or
+ * 5. [ a , b ] if a and b are anchors such that a < x < b and b - a is minimal.
+ */
+private fun findBounds(
+ offset: Float,
+ anchors: Set<Float>
+): List<Float> {
+ // Find the anchors the target lies between with a little bit of rounding error.
+ val a = anchors.filter { it <= offset + 0.001 }.maxOrNull()
+ val b = anchors.filter { it >= offset - 0.001 }.minOrNull()
+
+ return when {
+ a == null ->
+ // case 1 or 3
+ listOfNotNull(b)
+ b == null ->
+ // case 4
+ listOf(a)
+ a == b ->
+ // case 2
+ // Can't return offset itself here since it might not be exactly equal
+ // to the anchor, despite being considered an exact match.
+ listOf(a)
+ else ->
+ // case 5
+ listOf(a, b)
+ }
+}
+
+private fun computeTarget(
+ offset: Float,
+ lastValue: Float,
+ anchors: Set<Float>,
+ thresholds: (Float, Float) -> Float,
+ velocity: Float,
+ velocityThreshold: Float
+): Float {
+ val bounds = findBounds(offset, anchors)
+ return when (bounds.size) {
+ 0 -> lastValue
+ 1 -> bounds[0]
+ else -> {
+ val lower = bounds[0]
+ val upper = bounds[1]
+ if (lastValue <= offset) {
+ // Swiping from lower to upper (positive).
+ if (velocity >= velocityThreshold) {
+ return upper
+ } else {
+ val threshold = thresholds(lower, upper)
+ if (offset < threshold) lower else upper
+ }
+ } else {
+ // Swiping from upper to lower (negative).
+ if (velocity <= -velocityThreshold) {
+ return lower
+ } else {
+ val threshold = thresholds(upper, lower)
+ if (offset > threshold) upper else lower
+ }
+ }
+ }
+ }
+}
+
+private fun <T> Map<Float, T>.getOffset(state: T): Float? {
+ return entries.firstOrNull { it.value == state }?.key
+}
+
+/**
+ * Contains useful defaults for [swipeable] and [SwipeableState].
+ */
+object SwipeableDefaults {
+ /**
+ * The default animation used by [SwipeableState].
+ */
+ val AnimationSpec = SpringSpec<Float>()
+
+ /**
+ * The default velocity threshold (1.8 dp per millisecond) used by [swipeable].
+ */
+ val VelocityThreshold = 125.dp
+
+ /**
+ * A stiff resistance factor which indicates that swiping isn't available right now.
+ */
+ const val StiffResistanceFactor = 20f
+
+ /**
+ * A standard resistance factor which indicates that the user has run out of things to see.
+ */
+ const val StandardResistanceFactor = 10f
+
+ /**
+ * The default resistance config used by [swipeable].
+ *
+ * This returns `null` if there is one anchor. If there are at least two anchors, it returns
+ * a [ResistanceConfig] with the resistance basis equal to the distance between the two bounds.
+ */
+ fun resistanceConfig(
+ anchors: Set<Float>,
+ factorAtMin: Float = StandardResistanceFactor,
+ factorAtMax: Float = StandardResistanceFactor
+ ): ResistanceConfig? {
+ return if (anchors.size <= 1) {
+ null
+ } else {
+ val basis = anchors.maxOrNull()!! - anchors.minOrNull()!!
+ ResistanceConfig(basis, factorAtMin, factorAtMax)
+ }
+ }
+}
+
+// temp default nested scroll connection for swipeables which desire as an opt in
+// revisit in b/174756744 as all types will have their own specific connection probably
+internal val <T> SwipeableState<T>.PreUpPostDownNestedScrollConnection: NestedScrollConnection
+ get() = object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ val delta = available.toFloat()
+ return if (delta < 0 && source == NestedScrollSource.Drag) {
+ performDrag(delta).toOffset()
+ } else {
+ Offset.Zero
+ }
+ }
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource
+ ): Offset {
+ return if (source == NestedScrollSource.Drag) {
+ performDrag(available.toFloat()).toOffset()
+ } else {
+ Offset.Zero
+ }
+ }
+
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ val toFling = Offset(available.x, available.y).toFloat()
+ return if (toFling < 0 && offset.value > minBound) {
+ performFling(velocity = toFling)
+ // since we go to the anchor with tween settling, consume all for the best UX
+ available
+ } else {
+ Velocity.Zero
+ }
+ }
+
+ override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ performFling(velocity = Offset(available.x, available.y).toFloat())
+ return available
+ }
+
+ private fun Float.toOffset(): Offset = Offset(0f, this)
+
+ private fun Offset.toFloat(): Float = this.y
+ }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index db0f337e..d7e5ee8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -22,20 +22,27 @@
val icon: Drawable,
val name: String,
val displayName: String,
- val credentialTypeIcon: Drawable,
val createOptions: List<CreateOptionInfo>,
val isDefault: Boolean,
)
-data class CreateOptionInfo(
- val icon: Drawable,
- val title: String,
- val subtitle: String,
+open class EntryInfo (
val entryKey: String,
val entrySubkey: String,
- val usageData: String
)
+class CreateOptionInfo(
+ entryKey: String,
+ entrySubkey: String,
+ val userProviderDisplayName: String,
+ val credentialTypeIcon: Drawable,
+ val profileIcon: Drawable,
+ val passwordCount: Int,
+ val passkeyCount: Int,
+ val totalCredentialCount: Int,
+ val lastUsedTimeMillis: Long?,
+) : EntryInfo(entryKey, entrySubkey)
+
data class RequestDisplayInfo(
val userName: String,
val displayName: String,
@@ -49,7 +56,7 @@
*/
data class ActiveEntry (
val activeProvider: ProviderInfo,
- val activeCreateOptionInfo: CreateOptionInfo,
+ val activeEntryInfo: EntryInfo,
)
/** The name of the current screen. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index aeea46a..278b835 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -1,6 +1,5 @@
package com.android.credentialmanager.createflow
-import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -9,24 +8,19 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Button
-import androidx.compose.material.ButtonColors
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Card
-import androidx.compose.material.Chip
-import androidx.compose.material.ChipDefaults
-import androidx.compose.material.Divider
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.ModalBottomSheetLayout
-import androidx.compose.material.ModalBottomSheetValue
-import androidx.compose.material.Text
-import androidx.compose.material.TextButton
-import androidx.compose.material.TopAppBar
+import androidx.compose.material3.Card
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@@ -39,16 +33,13 @@
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.material.ModalBottomSheetLayout
+import com.android.credentialmanager.common.material.ModalBottomSheetValue
+import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL
import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
-import com.android.credentialmanager.ui.theme.Grey100
-import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.Typography
-import com.android.credentialmanager.ui.theme.lightBackgroundColor
-import com.android.credentialmanager.ui.theme.lightColorAccentSecondary
-import com.android.credentialmanager.ui.theme.lightSurface1
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CreatePasskeyScreen(
viewModel: CreatePasskeyViewModel,
@@ -74,7 +65,7 @@
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerInfo = uiState.activeEntry?.activeProvider!!,
- createOptionInfo = uiState.activeEntry.activeCreateOptionInfo,
+ createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
onCancel = viewModel::onCancel,
@@ -92,8 +83,8 @@
)
}
},
- scrimColor = Color.Transparent,
- sheetShape = Shapes.medium,
+ scrimColor = MaterialTheme.colorScheme.scrim,
+ sheetShape = MaterialTheme.shapes.medium,
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
@@ -107,9 +98,7 @@
onConfirm: () -> Unit,
onCancel: () -> Unit,
) {
- Card(
- backgroundColor = lightBackgroundColor,
- ) {
+ Card() {
Column() {
Icon(
painter = painterResource(R.drawable.ic_passkey),
@@ -119,7 +108,7 @@
)
Text(
text = stringResource(R.string.passkey_creation_intro_title),
- style = Typography.subtitle1,
+ style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally)
@@ -130,7 +119,7 @@
)
Text(
text = stringResource(R.string.passkey_creation_intro_body),
- style = Typography.body1,
+ style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(horizontal = 28.dp)
)
Divider(
@@ -143,11 +132,11 @@
) {
CancelButton(
stringResource(R.string.string_cancel),
- onclick = onCancel
+ onClick = onCancel
)
ConfirmButton(
stringResource(R.string.string_continue),
- onclick = onConfirm
+ onClick = onConfirm
)
}
Divider(
@@ -159,25 +148,23 @@
}
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProviderSelectionCard(
providerList: List<ProviderInfo>,
onProviderSelected: (String) -> Unit,
onCancel: () -> Unit
) {
- Card(
- backgroundColor = lightBackgroundColor,
- ) {
+ Card() {
Column() {
Text(
text = stringResource(R.string.choose_provider_title),
- style = Typography.subtitle1,
+ style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
Text(
text = stringResource(R.string.choose_provider_body),
- style = Typography.body1,
+ style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(horizontal = 28.dp)
)
Divider(
@@ -185,10 +172,10 @@
color = Color.Transparent
)
Card(
- shape = Shapes.medium,
+ shape = MaterialTheme.shapes.large,
modifier = Modifier
.padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
+ .align(alignment = Alignment.CenterHorizontally),
) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
@@ -219,28 +206,27 @@
}
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsSelectionCard(
providerList: List<ProviderInfo>,
onBackButtonSelected: () -> Unit,
onOptionSelected: (ActiveEntry) -> Unit
) {
- Card(
- backgroundColor = lightBackgroundColor,
- ) {
+ Card() {
Column() {
TopAppBar(
title = {
- Text(text = stringResource(R.string.string_more_options), style = Typography.subtitle1)
+ Text(
+ text = stringResource(R.string.string_more_options),
+ style = MaterialTheme.typography.titleMedium
+ )
},
- backgroundColor = lightBackgroundColor,
- elevation = 0.dp,
- navigationIcon =
- {
+ navigationIcon = {
IconButton(onClick = onBackButtonSelected) {
- Icon(Icons.Filled.ArrowBack, "backIcon"
- )
+ Icon(
+ Icons.Filled.ArrowBack,
+ "backIcon")
}
}
)
@@ -250,12 +236,12 @@
)
Text(
text = stringResource(R.string.create_passkey_at),
- style = Typography.body1,
+ style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(horizontal = 28.dp),
textAlign = TextAlign.Center
)
Card(
- shape = Shapes.medium,
+ shape = MaterialTheme.shapes.large,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally)
@@ -287,19 +273,17 @@
}
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsRowIntroCard(
providerInfo: ProviderInfo,
onDefaultOrNotSelected: () -> Unit,
) {
- Card(
- backgroundColor = lightBackgroundColor,
- ) {
+ Card() {
Column() {
Text(
text = stringResource(R.string.use_provider_for_all_title, providerInfo.displayName),
- style = Typography.subtitle1,
+ style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
Row(
@@ -308,11 +292,11 @@
) {
CancelButton(
stringResource(R.string.use_once),
- onclick = onDefaultOrNotSelected
+ onClick = onDefaultOrNotSelected
)
ConfirmButton(
stringResource(R.string.set_as_default),
- onclick = onDefaultOrNotSelected
+ onClick = onDefaultOrNotSelected
)
}
Divider(
@@ -324,74 +308,45 @@
}
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
- Chip(
+ SuggestionChip(
modifier = Modifier.fillMaxWidth(),
onClick = {onProviderSelected(providerInfo.name)},
- leadingIcon = {
+ icon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
// painter = painterResource(R.drawable.ic_passkey),
// TODO: add description.
contentDescription = "")
},
- colors = ChipDefaults.chipColors(
- backgroundColor = Grey100,
- leadingIconContentColor = Grey100
- ),
- shape = Shapes.large
- ) {
- Text(
- text = providerInfo.displayName,
- style = Typography.button,
- modifier = Modifier.padding(vertical = 18.dp)
- )
+ shape = MaterialTheme.shapes.large,
+ label = {
+ Text(
+ text = providerInfo.displayName,
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier.padding(vertical = 18.dp)
+ )
+ }
+ )
+}
+
+@Composable
+fun CancelButton(text: String, onClick: () -> Unit) {
+ TextButton(onClick = onClick) {
+ Text(text = text)
}
}
@Composable
-fun CancelButton(text: String, onclick: () -> Unit) {
- val colors = ButtonDefaults.buttonColors(
- backgroundColor = lightBackgroundColor
- )
- NavigationButton(
- border = BorderStroke(1.dp, lightSurface1),
- colors = colors,
- text = text,
- onclick = onclick)
-}
-
-@Composable
-fun ConfirmButton(text: String, onclick: () -> Unit) {
- val colors = ButtonDefaults.buttonColors(
- backgroundColor = lightColorAccentSecondary
- )
- NavigationButton(
- colors = colors,
- text = text,
- onclick = onclick)
-}
-
-@Composable
-fun NavigationButton(
- border: BorderStroke? = null,
- colors: ButtonColors,
- text: String,
- onclick: () -> Unit
-) {
- Button(
- onClick = onclick,
- shape = Shapes.small,
- colors = colors,
- border = border
- ) {
- Text(text = text, style = Typography.button)
+fun ConfirmButton(text: String, onClick: () -> Unit) {
+ FilledTonalButton(onClick = onClick) {
+ Text(text = text)
}
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CreationSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
@@ -403,12 +358,10 @@
multiProvider: Boolean,
onMoreOptionsSelected: () -> Unit,
) {
- Card(
- backgroundColor = lightBackgroundColor,
- ) {
+ Card() {
Column() {
Icon(
- bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+ bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
@@ -422,41 +375,45 @@
else -> stringResource(R.string.choose_create_option_sign_in_title,
providerInfo.displayName)
},
- style = Typography.subtitle1,
+ style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
)
Text(
text = requestDisplayInfo.appDomainName,
- style = Typography.body2,
+ style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
)
Text(
text = stringResource(
R.string.choose_create_option_description,
when (requestDisplayInfo.type) {
- TYPE_PUBLIC_KEY_CREDENTIAL -> "passkeys"
- TYPE_PASSWORD_CREDENTIAL -> "passwords"
- else -> "sign-ins"
+ TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys)
+ TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords)
+ else -> stringResource(R.string.sign_ins)
},
providerInfo.displayName,
- createOptionInfo.title),
- style = Typography.body1,
+ createOptionInfo.userProviderDisplayName
+ ),
+ style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
Card(
- shape = Shapes.medium,
+ shape = MaterialTheme.shapes.large,
modifier = Modifier
.padding(horizontal = 24.dp)
- .align(alignment = Alignment.CenterHorizontally)
+ .align(alignment = Alignment.CenterHorizontally),
) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
item {
- PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
- onOptionSelected = onOptionSelected)
+ PrimaryCreateOptionRow(
+ requestDisplayInfo = requestDisplayInfo,
+ createOptionInfo = createOptionInfo,
+ onOptionSelected = onOptionSelected
+ )
}
}
}
@@ -486,11 +443,11 @@
) {
CancelButton(
stringResource(R.string.string_cancel),
- onclick = onCancel
+ onClick = onCancel
)
ConfirmButton(
stringResource(R.string.string_continue),
- onclick = onConfirm
+ onClick = onConfirm
)
}
Divider(
@@ -502,76 +459,76 @@
}
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
+ createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
- Chip(
+ SuggestionChip(
modifier = Modifier.fillMaxWidth(),
onClick = onOptionSelected,
- // TODO: Add an icon generated by provider according to requestDisplayInfo type
- colors = ChipDefaults.chipColors(
- backgroundColor = Grey100,
- leadingIconContentColor = Grey100
- ),
- shape = Shapes.large
- ) {
- Column() {
- Text(
- text = requestDisplayInfo.userName,
- style = Typography.h6,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = requestDisplayInfo.displayName,
- style = Typography.body2,
- modifier = Modifier.padding(bottom = 16.dp)
- )
+ icon = {
+ Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+ contentDescription = stringResource(R.string.createOptionInfo_icon_description))
+ },
+ shape = MaterialTheme.shapes.large,
+ label = {
+ Column() {
+ Text(
+ text = requestDisplayInfo.userName,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = requestDisplayInfo.displayName,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
}
- }
+ )
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionsInfoRow(
providerInfo: ProviderInfo,
createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
- Chip(
+ SuggestionChip(
modifier = Modifier.fillMaxWidth(),
onClick = onOptionSelected,
- leadingIcon = {
+ icon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
- bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
+ bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
// painter = painterResource(R.drawable.ic_passkey),
// TODO: add description.
contentDescription = "")
},
- colors = ChipDefaults.chipColors(
- backgroundColor = Grey100,
- leadingIconContentColor = Grey100
- ),
- shape = Shapes.large
- ) {
- Column() {
+ shape = MaterialTheme.shapes.large,
+ label = {
+ Column() {
Text(
text =
if (providerInfo.createOptions.size > 1)
{stringResource(R.string.more_options_title_multiple_options,
- providerInfo.displayName, createOptionInfo.title)} else {
+ providerInfo.displayName, createOptionInfo.userProviderDisplayName)} else {
stringResource(R.string.more_options_title_one_option,
providerInfo.displayName)},
- style = Typography.h6,
+ style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(top = 16.dp)
)
Text(
- text = createOptionInfo.usageData,
- style = Typography.body2,
+ text = stringResource(R.string.more_options_usage_data,
+ createOptionInfo.passwordCount, createOptionInfo.passkeyCount),
+ style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 16.dp)
)
+ }
}
- }
+ )
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 615da4e..2e9758a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -73,21 +73,6 @@
)
}
- fun onCreateOptionSelected(entryKey: String, entrySubkey: String) {
- Log.d(
- "Account Selector",
- "Option selected for creation: {key = $entryKey, subkey = $entrySubkey}"
- )
- CredentialManagerRepo.getInstance().onOptionSelected(
- uiState.activeEntry?.activeProvider!!.name,
- entryKey,
- entrySubkey
- )
- dialogResult.value = DialogResult(
- ResultState.COMPLETE,
- )
- }
-
fun getProviderInfoByName(providerName: String): ProviderInfo {
return uiState.providers.single {
it.name.equals(providerName)
@@ -126,18 +111,18 @@
}
fun onPrimaryCreateOptionInfoSelected() {
- var createOptionEntryKey = uiState.activeEntry?.activeCreateOptionInfo?.entryKey
- var createOptionEntrySubkey = uiState.activeEntry?.activeCreateOptionInfo?.entrySubkey
+ val entryKey = uiState.activeEntry?.activeEntryInfo?.entryKey
+ val entrySubkey = uiState.activeEntry?.activeEntryInfo?.entrySubkey
Log.d(
"Account Selector",
"Option selected for creation: " +
- "{key = $createOptionEntryKey, subkey = $createOptionEntrySubkey}"
+ "{key = $entryKey, subkey = $entrySubkey}"
)
- if (createOptionEntryKey != null && createOptionEntrySubkey != null) {
+ if (entryKey != null && entrySubkey != null) {
CredentialManagerRepo.getInstance().onOptionSelected(
uiState.activeEntry?.activeProvider!!.name,
- createOptionEntryKey,
- createOptionEntrySubkey
+ entryKey,
+ entrySubkey
)
} else {
TODO("Gracefully handle illegal state.")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index e3398c0..7635021 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -25,16 +25,13 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Card
-import androidx.compose.material.Chip
-import androidx.compose.material.ChipDefaults
-import androidx.compose.material.Divider
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.ModalBottomSheetLayout
-import androidx.compose.material.ModalBottomSheetValue
-import androidx.compose.material.Text
-import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.material3.Card
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@@ -45,13 +42,12 @@
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.material.ModalBottomSheetLayout
+import com.android.credentialmanager.common.material.ModalBottomSheetValue
+import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.createflow.CancelButton
-import com.android.credentialmanager.ui.theme.Grey100
-import com.android.credentialmanager.ui.theme.Shapes
-import com.android.credentialmanager.ui.theme.Typography
-import com.android.credentialmanager.ui.theme.lightBackgroundColor
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GetCredentialScreen(
viewModel: GetCredentialViewModel,
@@ -76,7 +72,7 @@
}
},
scrimColor = Color.Transparent,
- sheetShape = Shapes.medium,
+ sheetShape = MaterialTheme.shapes.medium,
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
@@ -85,7 +81,7 @@
}
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CredentialSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
@@ -95,9 +91,7 @@
multiProvider: Boolean,
onMoreOptionSelected: () -> Unit,
) {
- Card(
- backgroundColor = lightBackgroundColor,
- ) {
+ Card() {
Column() {
Icon(
bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
@@ -107,14 +101,14 @@
)
Text(
text = stringResource(R.string.choose_sign_in_title),
- style = Typography.subtitle1,
+ style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.padding(all = 24.dp)
.align(alignment = Alignment.CenterHorizontally)
)
Text(
text = requestDisplayInfo.appDomainName,
- style = Typography.body2,
+ style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(horizontal = 28.dp)
)
Divider(
@@ -122,7 +116,7 @@
color = Color.Transparent
)
Card(
- shape = Shapes.medium,
+ shape = MaterialTheme.shapes.medium,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally)
@@ -161,57 +155,52 @@
}
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CredentialOptionRow(
credentialOptionInfo: CredentialOptionInfo,
onOptionSelected: (String, String) -> Unit,
) {
- Chip(
+ SuggestionChip(
modifier = Modifier.fillMaxWidth(),
onClick = {onOptionSelected(credentialOptionInfo.entryKey, credentialOptionInfo.entrySubkey)},
- leadingIcon = {
+ icon = {
Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
// TODO: add description.
contentDescription = "")
},
- colors = ChipDefaults.chipColors(
- backgroundColor = Grey100,
- leadingIconContentColor = Grey100
- ),
- shape = Shapes.large
- ) {
- Column() {
- Text(
- text = credentialOptionInfo.title,
- style = Typography.h6,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = credentialOptionInfo.subtitle,
- style = Typography.body2,
- modifier = Modifier.padding(bottom = 16.dp)
- )
+ shape = MaterialTheme.shapes.large,
+ label = {
+ Column() {
+ // TODO: fix the text values.
+ Text(
+ text = credentialOptionInfo.entryKey,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = credentialOptionInfo.entrySubkey,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
}
- }
+ )
}
-@ExperimentalMaterialApi
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionRow(onSelect: () -> Unit) {
- Chip(
+ SuggestionChip(
modifier = Modifier.fillMaxWidth().height(52.dp),
onClick = onSelect,
- colors = ChipDefaults.chipColors(
- backgroundColor = Grey100,
- leadingIconContentColor = Grey100
- ),
- shape = Shapes.large
- ) {
- Text(
- text = stringResource(R.string.string_more_options),
- style = Typography.h6,
- )
- }
+ shape = MaterialTheme.shapes.large,
+ label = {
+ Text(
+ text = stringResource(R.string.string_more_options),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ }
+ )
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b6ecd37..b427de6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -24,17 +24,21 @@
val displayName: String,
val credentialTypeIcon: Drawable,
val credentialOptions: List<CredentialOptionInfo>,
+ // TODO: Add the authenticationOption
)
-data class CredentialOptionInfo(
- val icon: Drawable,
- val title: String,
- val subtitle: String,
+open class EntryInfo (
val entryKey: String,
val entrySubkey: String,
- val usageData: String
)
+class CredentialOptionInfo(
+ entryKey: String,
+ entrySubkey: String,
+ val icon: Drawable,
+ val usageData: String,
+) : EntryInfo(entryKey, entrySubkey)
+
data class RequestDisplayInfo(
val userName: String,
val displayName: String,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
index cba8658..5ea6993 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -1,7 +1,7 @@
package com.android.credentialmanager.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Shapes
+import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
index a9d20ae..248df92 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -1,30 +1,21 @@
package com.android.credentialmanager.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
-private val DarkColorPalette = darkColors(
+private val AppDarkColorScheme = darkColorScheme(
primary = Purple200,
- primaryVariant = Purple700,
- secondary = Teal200
+ secondary = Purple700,
+ tertiary = Teal200
)
-private val LightColorPalette = lightColors(
+private val AppLightColorScheme = lightColorScheme(
primary = Purple500,
- primaryVariant = Purple700,
- secondary = Teal200
-
- /* Other default colors to override
- background = Color.White,
- surface = Color.White,
- onPrimary = Color.White,
- onSecondary = Color.Black,
- onBackground = Color.Black,
- onSurface = Color.Black,
- */
+ secondary = Purple700,
+ tertiary = Teal200
)
@Composable
@@ -32,14 +23,14 @@
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
- val colors = if (darkTheme) {
- DarkColorPalette
+ val AppColorScheme = if (darkTheme) {
+ AppDarkColorScheme
} else {
- LightColorPalette
+ AppLightColorScheme
}
MaterialTheme(
- colors = colors,
+ colorScheme = AppColorScheme,
typography = Typography,
shapes = Shapes,
content = content
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
index d8fb01c..e09abbb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
@@ -1,6 +1,6 @@
package com.android.credentialmanager.ui.theme
-import androidx.compose.material.Typography
+import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
@@ -8,32 +8,32 @@
// Set of Material typography styles to start with
val Typography = Typography(
- subtitle1 = TextStyle(
+ titleMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 24.sp,
lineHeight = 32.sp,
),
- body1 = TextStyle(
+ bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
lineHeight = 20.sp,
),
- body2 = TextStyle(
+ bodyMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
lineHeight = 20.sp,
color = textColorSecondary
),
- button = TextStyle(
+ labelLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
),
- h6 = TextStyle(
+ titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 16.sp,
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index f0b6ac5..e04a9be 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -57,6 +57,5 @@
}
dependencies {
- implementation(project(":spa"))
- implementation "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
+ implementation project(":spa")
}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 682d3fc..7a20c747 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -60,9 +60,9 @@
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
- api "androidx.navigation:navigation-compose:2.5.0"
+ api "androidx.navigation:navigation-compose:2.6.0-alpha03"
+ api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
api "com.google.android.material:material:1.7.0-alpha03"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
implementation "com.airbnb.android:lottie-compose:5.2.0"
- implementation "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index e63e4c9..14b1629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -30,10 +30,14 @@
val injectEntry: SettingsEntry,
)
+private fun SettingsPage.getTitle(sppRepository: SettingsPageProviderRepository): String {
+ return sppRepository.getProviderOrNull(sppName)!!.getTitle(arguments)
+}
+
/**
* The repository to maintain all Settings entries
*/
-class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
+class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRepository) {
// Map of entry unique Id to entry
private val entryMap: Map<String, SettingsEntry>
@@ -118,8 +122,9 @@
return entryPath.map {
if (it.toPage == null)
defaultTitle
- else
- it.toPage.getTitle()!!
+ else {
+ it.toPage.getTitle(sppRepository)
+ }
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 2fa9229..bb287d1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -95,11 +95,6 @@
return false
}
- fun getTitle(): String? {
- val sppRepository by SpaEnvironmentFactory.instance.pageProviderRepository
- return sppRepository.getProviderOrNull(sppName)?.getTitle(arguments)
- }
-
fun enterPage() {
SpaEnvironmentFactory.instance.logger.event(
id,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
new file mode 100644
index 0000000..9419161
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryRepositoryTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaEnvironment = SpaEnvironmentForTest(context)
+ private val entryRepository by spaEnvironment.entryRepository
+
+ @Test
+ fun testGetPageWithEntry() {
+ val pageWithEntry = entryRepository.getAllPageWithEntry()
+ assertThat(pageWithEntry.size).isEqualTo(3)
+ assertThat(
+ entryRepository.getPageWithEntry(getUniquePageId("SppHome"))
+ ?.entries?.size
+ ).isEqualTo(1)
+ assertThat(
+ entryRepository.getPageWithEntry(getUniquePageId("SppLayer1"))
+ ?.entries?.size
+ ).isEqualTo(3)
+ assertThat(
+ entryRepository.getPageWithEntry(getUniquePageId("SppLayer2"))
+ ?.entries?.size
+ ).isEqualTo(2)
+ assertThat(entryRepository.getPageWithEntry(getUniquePageId("SppWithParam"))).isNull()
+ }
+
+ @Test
+ fun testGetEntry() {
+ val entry = entryRepository.getAllEntries()
+ assertThat(entry.size).isEqualTo(7)
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId(
+ "ROOT",
+ SppHome.createSettingsPage(),
+ SettingsPage.createNull(),
+ SppHome.createSettingsPage(),
+ )
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId(
+ "INJECT",
+ SppLayer1.createSettingsPage(),
+ SppHome.createSettingsPage(),
+ SppLayer1.createSettingsPage(),
+ )
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId(
+ "INJECT",
+ SppLayer2.createSettingsPage(),
+ SppLayer1.createSettingsPage(),
+ SppLayer2.createSettingsPage(),
+ )
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+ )
+ ).isNotNull()
+ assertThat(
+ entryRepository.getEntry(
+ getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
+ )
+ ).isNotNull()
+ }
+
+ @Test
+ fun testGetEntryPath() {
+ assertThat(
+ entryRepository.getEntryPathWithDisplayName(
+ getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+ )
+ ).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
+ .inOrder()
+
+ assertThat(
+ entryRepository.getEntryPathWithTitle(
+ getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
+ "entryTitle"
+ )
+ ).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+
+ assertThat(
+ entryRepository.getEntryPathWithDisplayName(
+ getUniqueEntryId(
+ "INJECT",
+ SppLayer1.createSettingsPage(),
+ SppHome.createSettingsPage(),
+ SppLayer1.createSettingsPage(),
+ )
+ )
+ ).containsExactly("INJECT_SppLayer1", "ROOT_SppHome").inOrder()
+
+ assertThat(
+ entryRepository.getEntryPathWithTitle(
+ getUniqueEntryId(
+ "INJECT",
+ SppLayer2.createSettingsPage(),
+ SppLayer1.createSettingsPage(),
+ SppLayer2.createSettingsPage(),
+ ),
+ "defaultTitle"
+ )
+ ).containsExactly("SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
new file mode 100644
index 0000000..31d2ae4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.core.os.bundleOf
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val INJECT_ENTRY_NAME = "INJECT"
+const val ROOT_ENTRY_NAME = "ROOT"
+
+class MacroForTest(private val pageId: String, private val entryId: String) : EntryMacro {
+ @Composable
+ override fun UiLayout() {
+ val entryData = LocalEntryDataProvider.current
+ assertThat(entryData.isHighlighted).isFalse()
+ assertThat(entryData.pageId).isEqualTo(pageId)
+ assertThat(entryData.entryId).isEqualTo(entryId)
+ }
+
+ override fun getSearchData(): EntrySearchData {
+ return EntrySearchData("myTitle")
+ }
+
+ override fun getStatusData(): EntryStatusData {
+ return EntryStatusData(isDisabled = true, isSwitchOff = true)
+ }
+}
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun testBuildBasic() {
+ val owner = SettingsPage.create("mySpp")
+ val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
+ assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry.displayName).isEqualTo("myEntry")
+ assertThat(entry.owner.sppName).isEqualTo("mySpp")
+ assertThat(entry.owner.displayName).isEqualTo("mySpp")
+ assertThat(entry.fromPage).isNull()
+ assertThat(entry.toPage).isNull()
+ assertThat(entry.isAllowSearch).isFalse()
+ assertThat(entry.isSearchDataDynamic).isFalse()
+ assertThat(entry.mutableStatus).isFalse()
+ }
+
+ @Test
+ fun testBuildWithLink() {
+ val owner = SettingsPage.create("mySpp")
+ val fromPage = SettingsPage.create("fromSpp")
+ val toPage = SettingsPage.create("toSpp")
+ val entryFrom = SettingsEntryBuilder.createLinkFrom("myEntry", owner)
+ .setLink(toPage = toPage).build()
+ assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
+ assertThat(entryFrom.displayName).isEqualTo("myEntry")
+ assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
+ assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
+
+ val entryTo = SettingsEntryBuilder.createLinkTo("myEntry", owner)
+ .setLink(fromPage = fromPage).build()
+ assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
+ assertThat(entryTo.displayName).isEqualTo("myEntry")
+ assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
+ assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
+ }
+
+ @Test
+ fun testBuildInject() {
+ val owner = SettingsPage.create("mySpp")
+ val entryInject = SettingsEntryBuilder.createInject(owner).build()
+ assertThat(entryInject.id).isEqualTo(
+ getUniqueEntryId(
+ INJECT_ENTRY_NAME,
+ owner,
+ toPage = owner
+ )
+ )
+ assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME}_mySpp")
+ assertThat(entryInject.fromPage).isNull()
+ assertThat(entryInject.toPage).isNotNull()
+ }
+
+ @Test
+ fun testBuildRoot() {
+ val owner = SettingsPage.create("mySpp")
+ val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
+ assertThat(entryInject.id).isEqualTo(
+ getUniqueEntryId(
+ ROOT_ENTRY_NAME,
+ owner,
+ toPage = owner
+ )
+ )
+ assertThat(entryInject.displayName).isEqualTo("myRootEntry")
+ assertThat(entryInject.fromPage).isNull()
+ assertThat(entryInject.toPage).isNotNull()
+ }
+
+ @Test
+ fun testSetAttributes() {
+ val owner = SettingsPage.create("mySpp")
+ val entry = SettingsEntryBuilder.create(owner, "myEntry")
+ .setDisplayName("myEntryDisplay")
+ .setIsAllowSearch(true)
+ .setIsSearchDataDynamic(false)
+ .setHasMutableStatus(true)
+ .build()
+ assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+ assertThat(entry.displayName).isEqualTo("myEntryDisplay")
+ assertThat(entry.fromPage).isNull()
+ assertThat(entry.toPage).isNull()
+ assertThat(entry.isAllowSearch).isTrue()
+ assertThat(entry.isSearchDataDynamic).isFalse()
+ assertThat(entry.mutableStatus).isTrue()
+ }
+
+ @Test
+ fun testSetMarco() {
+ val owner = SettingsPage.create("mySpp", arguments = bundleOf("param" to "v1"))
+ val entry = SettingsEntryBuilder.create(owner, "myEntry")
+ .setMacro {
+ assertThat(it?.getString("param")).isEqualTo("v1")
+ assertThat(it?.getString("rtParam")).isEqualTo("v2")
+ assertThat(it?.getString("unknown")).isNull()
+ MacroForTest(getUniquePageId("mySpp"), getUniqueEntryId("myEntry", owner))
+ }
+ .build()
+
+ val rtArguments = bundleOf("rtParam" to "v2")
+ composeTestRule.setContent { entry.UiLayout(rtArguments) }
+ val searchData = entry.getSearchData(rtArguments)
+ val statusData = entry.getStatusData(rtArguments)
+ assertThat(searchData?.title).isEqualTo("myTitle")
+ assertThat(searchData?.keyword).isEmpty()
+ assertThat(statusData?.isDisabled).isTrue()
+ assertThat(statusData?.isSwitchOff).isTrue()
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
new file mode 100644
index 0000000..6c0c652
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageProviderRepositoryTest {
+ @Test
+ fun getStartPageTest() {
+ val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+ assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
+ assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
+
+ val sppRepoNull =
+ SettingsPageProviderRepository(emptyList(), listOf(SettingsPage.createNull()))
+ assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
+ assertThat(sppRepoNull.getAllRootPages()).contains(SettingsPage.createNull())
+
+ val rootPage1 = SettingsPage.create(name = "Spp1", displayName = "Spp1")
+ val rootPage2 = SettingsPage.create(name = "Spp2", displayName = "Spp2")
+ val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
+ val allRoots = sppRepo.getAllRootPages()
+ assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
+ assertThat(allRoots.size).isEqualTo(2)
+ assertThat(allRoots).contains(rootPage1)
+ assertThat(allRoots).contains(rootPage2)
+ }
+
+ @Test
+ fun getProviderTest() {
+ val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+ assertThat(sppRepoEmpty.getAllProviders()).isEmpty()
+ assertThat(sppRepoEmpty.getProviderOrNull("Spp")).isNull()
+
+ val sppRepo = SettingsPageProviderRepository(listOf(
+ object : SettingsPageProvider {
+ override val name = "Spp"
+ }
+ ), emptyList())
+ assertThat(sppRepo.getAllProviders().size).isEqualTo(1)
+ assertThat(sppRepo.getProviderOrNull("Spp")).isNotNull()
+ assertThat(sppRepo.getProviderOrNull("SppUnknown")).isNull()
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
new file mode 100644
index 0000000..539e56b
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaLogger = SpaLoggerForTest()
+ private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+
+ @Test
+ fun testNullPage() {
+ val page = SettingsPage.createNull()
+ assertThat(page.id).isEqualTo(getUniquePageId("NULL"))
+ assertThat(page.sppName).isEqualTo("NULL")
+ assertThat(page.displayName).isEqualTo("NULL")
+ assertThat(page.buildRoute()).isEqualTo("NULL")
+ assertThat(page.isCreateBy("NULL")).isTrue()
+ assertThat(page.isCreateBy("Spp")).isFalse()
+ assertThat(page.hasRuntimeParam()).isFalse()
+ assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+ assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+ assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+ }
+
+ @Test
+ fun testRegularPage() {
+ val page = SettingsPage.create("mySpp", "SppDisplayName")
+ assertThat(page.id).isEqualTo(getUniquePageId("mySpp"))
+ assertThat(page.sppName).isEqualTo("mySpp")
+ assertThat(page.displayName).isEqualTo("SppDisplayName")
+ assertThat(page.buildRoute()).isEqualTo("mySpp")
+ assertThat(page.isCreateBy("NULL")).isFalse()
+ assertThat(page.isCreateBy("mySpp")).isTrue()
+ assertThat(page.hasRuntimeParam()).isFalse()
+ assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+ assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+ assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+ "-e spaActivityDestination mySpp"
+ )
+ }
+
+ @Test
+ fun testParamPage() {
+ val arguments = bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ )
+ val page = spaEnvironment.createPage("SppWithParam", arguments)
+ assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ ), arguments))
+ assertThat(page.sppName).isEqualTo("SppWithParam")
+ assertThat(page.displayName).isEqualTo("SppWithParam")
+ assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
+ assertThat(page.isCreateBy("SppWithParam")).isTrue()
+ assertThat(page.hasRuntimeParam()).isFalse()
+ assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+ assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+ assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+ "-e spaActivityDestination SppWithParam/myStr/10"
+ )
+ }
+
+ @Test
+ fun testRtParamPage() {
+ val arguments = bundleOf(
+ "string_param" to "myStr",
+ "int_param" to 10,
+ "rt_param" to "rtStr",
+ )
+ val page = spaEnvironment.createPage("SppWithRtParam", arguments)
+ assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ ), arguments))
+ assertThat(page.sppName).isEqualTo("SppWithRtParam")
+ assertThat(page.displayName).isEqualTo("SppWithRtParam")
+ assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
+ assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
+ assertThat(page.hasRuntimeParam()).isTrue()
+ assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+ assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+ assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+ }
+
+ @Test
+ fun testPageEvent() {
+ spaLogger.reset()
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val page = spaEnvironment.createPage("SppHome")
+ page.enterPage()
+ page.leavePage()
+ page.enterPage()
+ assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+ .isEqualTo(2)
+ assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+ .isEqualTo(1)
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
new file mode 100644
index 0000000..b8731a3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.BrowseActivity
+
+class SpaLoggerForTest : SpaLogger {
+ data class MsgCountKey(val msg: String, val category: LogCategory)
+ data class EventCountKey(val id: String, val event: LogEvent, val category: LogCategory)
+
+ private val messageCount: MutableMap<MsgCountKey, Int> = mutableMapOf()
+ private val eventCount: MutableMap<EventCountKey, Int> = mutableMapOf()
+
+ override fun message(tag: String, msg: String, category: LogCategory) {
+ val key = MsgCountKey("[$tag]$msg", category)
+ messageCount[key] = messageCount.getOrDefault(key, 0) + 1
+ }
+
+ override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+ val key = EventCountKey(id, event, category)
+ eventCount[key] = eventCount.getOrDefault(key, 0) + 1
+ }
+
+ fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
+ val key = MsgCountKey("[$tag]$msg", category)
+ return messageCount.getOrDefault(key, 0)
+ }
+
+ fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
+ val key = EventCountKey(id, event, category)
+ return eventCount.getOrDefault(key, 0)
+ }
+
+ fun reset() {
+ messageCount.clear()
+ eventCount.clear()
+ }
+}
+
+class MockActivity : BrowseActivity()
+
+object SppHome : SettingsPageProvider {
+ override val name = "SppHome"
+
+ override fun getTitle(arguments: Bundle?): String {
+ return "TitleHome"
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SppLayer1.buildInject().setLink(fromPage = owner).build(),
+ )
+ }
+}
+
+object SppLayer1 : SettingsPageProvider {
+ override val name = "SppLayer1"
+
+ override fun getTitle(arguments: Bundle?): String {
+ return "TitleLayer1"
+ }
+
+ fun buildInject(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(this.createSettingsPage())
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SettingsEntryBuilder.create(owner, "Layer1Entry1").build(),
+ SppLayer2.buildInject().setLink(fromPage = owner).build(),
+ SettingsEntryBuilder.create(owner, "Layer1Entry2").build(),
+ )
+ }
+}
+
+object SppLayer2 : SettingsPageProvider {
+ override val name = "SppLayer2"
+
+ fun buildInject(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(this.createSettingsPage())
+ }
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SettingsEntryBuilder.create(owner, "Layer2Entry1").build(),
+ SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
+ )
+ }
+}
+
+class SpaEnvironmentForTest(
+ context: Context,
+ override val browseActivityClass: Class<out Activity>? = MockActivity::class.java,
+ override val logger: SpaLogger = SpaLoggerForTest()
+) : SpaEnvironment(context) {
+
+ override val pageProviderRepository = lazy {
+ SettingsPageProviderRepository(
+ listOf(
+ SppHome, SppLayer1, SppLayer2,
+ object : SettingsPageProvider {
+ override val name = "SppWithParam"
+ override val parameter = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ )
+ },
+ object : SettingsPageProvider {
+ override val name = "SppWithRtParam"
+ override val parameter = listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ )
+ },
+ ),
+ listOf(SettingsPage.create("SppHome"))
+ )
+ }
+
+ fun createPage(sppName: String, arguments: Bundle? = null): SettingsPage {
+ return pageProviderRepository.value
+ .getProviderOrNull(sppName)!!.createSettingsPage(arguments)
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
new file mode 100644
index 0000000..93f9afe
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.common
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.normalize
+
+fun getUniquePageId(
+ name: String,
+ parameter: List<NamedNavArgument> = emptyList(),
+ arguments: Bundle? = null
+): String {
+ val normArguments = parameter.normalize(arguments)
+ return "$name:${normArguments?.toString()}".toHashId()
+}
+
+fun getUniquePageId(page: SettingsPage): String {
+ return getUniquePageId(page.sppName, page.parameter, page.arguments)
+}
+
+fun getUniqueEntryId(
+ name: String,
+ owner: SettingsPage,
+ fromPage: SettingsPage? = null,
+ toPage: SettingsPage? = null
+): String {
+ val ownerId = getUniquePageId(owner)
+ val fromId = if (fromPage == null) "null" else getUniquePageId(fromPage)
+ val toId = if (toPage == null) "null" else getUniquePageId(toPage)
+ return "$name:$ownerId($fromId-$toId)".toHashId()
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
index 21ff085..48ebd8d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -20,9 +20,12 @@
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavType
import androidx.navigation.navArgument
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import org.junit.Test
+import org.junit.runner.RunWith
+@RunWith(AndroidJUnit4::class)
class ParameterTest {
@Test
fun navRouteTest() {
@@ -88,14 +91,14 @@
"Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
)
- val setParialParam = navArguments.normalize(
+ val setPartialParam = navArguments.normalize(
bundleOf(
"string_param" to "myStr",
"rt_param" to "rtStr",
)
)
- assertThat(setParialParam).isNotNull()
- assertThat(setParialParam.toString()).isEqualTo(
+ assertThat(setPartialParam).isNotNull()
+ assertThat(setPartialParam.toString()).isEqualTo(
"Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 3cd8378..9d6b311 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -41,6 +41,13 @@
import kotlinx.coroutines.Dispatchers
private const val TAG = "AppList"
+private const val CONTENT_TYPE_HEADER = "header"
+
+internal data class AppListState(
+ val showSystem: State<Boolean>,
+ val option: State<Int>,
+ val searchQuery: State<String>,
+)
/**
* The template to render an App List.
@@ -49,23 +56,26 @@
*/
@Composable
internal fun <T : AppRecord> AppList(
- appListConfig: AppListConfig,
+ config: AppListConfig,
listModel: AppListModel<T>,
- showSystem: State<Boolean>,
- option: State<Int>,
- searchQuery: State<String>,
+ state: AppListState,
+ header: @Composable () -> Unit,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
bottomPadding: Dp,
+ appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
+ loadAppListData(config, listModel, state)
+ },
) {
- LogCompositions(TAG, appListConfig.userId.toString())
- val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
- AppListWidget(appListData, listModel, appItem, bottomPadding)
+ LogCompositions(TAG, config.userId.toString())
+ val appListData = appListDataSupplier()
+ AppListWidget(appListData, listModel, header, appItem, bottomPadding)
}
@Composable
private fun <T : AppRecord> AppListWidget(
appListData: State<AppListData<T>?>,
listModel: AppListModel<T>,
+ header: @Composable () -> Unit,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
bottomPadding: Dp,
) {
@@ -81,6 +91,10 @@
state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
contentPadding = PaddingValues(bottom = bottomPadding),
) {
+ item(contentType = CONTENT_TYPE_HEADER) {
+ header()
+ }
+
items(count = list.size, key = { option to list[it].record.app.packageName }) {
val appEntry = list[it]
val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
@@ -94,19 +108,17 @@
}
@Composable
-private fun <T : AppRecord> loadAppEntries(
- appListConfig: AppListConfig,
+private fun <T : AppRecord> loadAppListData(
+ config: AppListConfig,
listModel: AppListModel<T>,
- showSystem: State<Boolean>,
- option: State<Int>,
- searchQuery: State<String>,
+ state: AppListState,
): State<AppListData<T>?> {
- val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
- viewModel.appListConfig.setIfAbsent(appListConfig)
+ val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
+ viewModel.appListConfig.setIfAbsent(config)
viewModel.listModel.setIfAbsent(listModel)
- viewModel.showSystem.Sync(showSystem)
- viewModel.option.Sync(option)
- viewModel.searchQuery.Sync(searchQuery)
+ viewModel.showSystem.Sync(state.showSystem)
+ viewModel.option.Sync(state.option)
+ viewModel.searchQuery.Sync(state.searchQuery)
return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2953367..388a7d8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -37,6 +37,8 @@
/**
* The full screen template for an App List page.
+ *
+ * @param header the description header appears before all the applications.
*/
@Composable
fun <T : AppRecord> AppListPage(
@@ -44,6 +46,7 @@
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
primaryUserOnly: Boolean = false,
+ header: @Composable () -> Unit = {},
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
) {
val showSystem = rememberSaveable { mutableStateOf(false) }
@@ -59,14 +62,17 @@
val selectedOption = rememberSaveable { mutableStateOf(0) }
Spinner(options, selectedOption.value) { selectedOption.value = it }
AppList(
- appListConfig = AppListConfig(
+ config = AppListConfig(
userId = userInfo.id,
showInstantApps = showInstantApps,
),
listModel = listModel,
- showSystem = showSystem,
- option = selectedOption,
- searchQuery = searchQuery,
+ state = AppListState(
+ showSystem = showSystem,
+ option = selectedOption,
+ searchQuery = searchQuery,
+ ),
+ header = header,
appItem = appItem,
bottomPadding = bottomPadding,
)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
new file mode 100644
index 0000000..80c4eac
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.icu.text.CollationKey
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppEntry
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
+import com.android.settingslib.spaprivileged.model.app.AppListData
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun whenNoApps() {
+ setContent(appEntries = emptyList())
+
+ composeTestRule.onNodeWithText(context.getString(R.string.no_applications))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun couldShowAppItem() {
+ setContent(appEntries = listOf(APP_ENTRY))
+
+ composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+ }
+
+ @Test
+ fun couldShowHeader() {
+ setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+
+ composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
+ }
+
+ private fun setContent(
+ header: @Composable () -> Unit = {},
+ appEntries: List<AppEntry<TestAppRecord>>,
+ ) {
+ composeTestRule.setContent {
+ AppList(
+ config = AppListConfig(userId = USER_ID, showInstantApps = false),
+ listModel = TestAppListModel(),
+ state = AppListState(
+ showSystem = false.toState(),
+ option = 0.toState(),
+ searchQuery = "".toState(),
+ ),
+ header = header,
+ appItem = { AppListItem(it) {} },
+ bottomPadding = 0.dp,
+ appListDataSupplier = {
+ stateOf(AppListData(appEntries, option = 0))
+ }
+ )
+ }
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val HEADER = "Header"
+ val APP_ENTRY = AppEntry(
+ record = TestAppRecord(ApplicationInfo()),
+ label = "AAA",
+ labelCollationKey = CollationKey("", byteArrayOf()),
+ )
+ }
+}
+
+private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+
+private class TestAppListModel : AppListModel<TestAppRecord> {
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ appListFlow.asyncMapItem { TestAppRecord(it) }
+
+ @Composable
+ override fun getSummary(option: Int, record: TestAppRecord) = null
+
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<TestAppRecord>>,
+ ) = recordListFlow
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 6bc1160..dd56bde 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -188,7 +188,6 @@
/**
* Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
* Hearing Aid Service is connected and the HiSyncId's are now available.
- * @param LocalBluetoothProfileManager profileManager
*/
public synchronized void updateHearingAidsDevices() {
mHearingAidDeviceManager.updateHearingAidsDevices();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
new file mode 100644
index 0000000..f06aab3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HapClientProfile handles the Bluetooth HAP service client role.
+ */
+public class HapClientProfile implements LocalBluetoothProfile {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ HearingAidType.TYPE_INVALID,
+ HearingAidType.TYPE_BINAURAL,
+ HearingAidType.TYPE_MONAURAL,
+ HearingAidType.TYPE_BANDED,
+ HearingAidType.TYPE_RFU
+ })
+
+ /** Hearing aid type definition for HAP Client. */
+ public @interface HearingAidType {
+ int TYPE_INVALID = -1;
+ int TYPE_BINAURAL = BluetoothHapClient.TYPE_BINAURAL;
+ int TYPE_MONAURAL = BluetoothHapClient.TYPE_MONAURAL;
+ int TYPE_BANDED = BluetoothHapClient.TYPE_BANDED;
+ int TYPE_RFU = BluetoothHapClient.TYPE_RFU;
+ }
+
+ static final String NAME = "HapClient";
+ private static final String TAG = "HapClientProfile";
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 1;
+
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final LocalBluetoothProfileManager mProfileManager;
+ private BluetoothHapClient mService;
+ private boolean mIsProfileReady;
+
+ // These callbacks run on the main thread.
+ private final class HapClientServiceListener implements BluetoothProfile.ServiceListener {
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ mService = (BluetoothHapClient) proxy;
+ // We just bound to the service, so refresh the UI for any connected HapClient devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // Adds a new device into mDeviceManager if it does not exist
+ if (device == null) {
+ Log.w(TAG, "HapClient profile found new device: " + nextDevice);
+ device = mDeviceManager.addDevice(nextDevice);
+ }
+ device.onProfileStateChanged(
+ HapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mIsProfileReady = true;
+ mProfileManager.callServiceConnectedListeners();
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ mIsProfileReady = false;
+ mProfileManager.callServiceDisconnectedListeners();
+ }
+ }
+
+ HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+ BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+ if (bluetoothManager != null) {
+ mBluetoothAdapter = bluetoothManager.getAdapter();
+ mBluetoothAdapter.getProfileProxy(context, new HapClientServiceListener(),
+ BluetoothProfile.HAP_CLIENT);
+ } else {
+ mBluetoothAdapter = null;
+ }
+ }
+
+ /**
+ * Get hearing aid devices matching connection states{
+ * {@code BluetoothProfile.STATE_CONNECTED},
+ * {@code BluetoothProfile.STATE_CONNECTING},
+ * {@code BluetoothProfile.STATE_DISCONNECTING}}
+ *
+ * @return Matching device list
+ */
+ public List<BluetoothDevice> getConnectedDevices() {
+ return getDevicesByStates(new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ /**
+ * Get hearing aid devices matching connection states{
+ * {@code BluetoothProfile.STATE_DISCONNECTED},
+ * {@code BluetoothProfile.STATE_CONNECTED},
+ * {@code BluetoothProfile.STATE_CONNECTING},
+ * {@code BluetoothProfile.STATE_DISCONNECTING}}
+ *
+ * @return Matching device list
+ */
+ public List<BluetoothDevice> getConnectableDevices() {
+ return getDevicesByStates(new int[] {
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ private List<BluetoothDevice> getDevicesByStates(int[] states) {
+ if (mService == null) {
+ return new ArrayList<>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(states);
+ }
+
+ /**
+ * Gets the hearing aid type of the device.
+ *
+ * @param device is the device for which we want to get the hearing aid type
+ * @return hearing aid type
+ */
+ @HearingAidType
+ public int getHearingAidType(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return HearingAidType.TYPE_INVALID;
+ }
+ return mService.getHearingAidType(device);
+ }
+
+ /**
+ * Gets if this device supports synchronized presets or not
+ *
+ * @param device is the device for which we want to know if supports synchronized presets
+ * @return {@code true} if the device supports synchronized presets
+ */
+ public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.supportSynchronizedPresets(device);
+ }
+
+ /**
+ * Gets if this device supports independent presets or not
+ *
+ * @param device is the device for which we want to know if supports independent presets
+ * @return {@code true} if the device supports independent presets
+ */
+ public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.supportIndependentPresets(device);
+ }
+
+ /**
+ * Gets if this device supports dynamic presets or not
+ *
+ * @param device is the device for which we want to know if supports dynamic presets
+ * @return {@code true} if the device supports dynamic presets
+ */
+ public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.supportDynamicPresets(device);
+ }
+
+ /**
+ * Gets if this device supports writable presets or not
+ *
+ * @param device is the device for which we want to know if supports writable presets
+ * @return {@code true} if the device supports writable presets
+ */
+ public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+ if (mService == null) {
+ return false;
+ }
+ return mService.supportWritablePresets(device);
+ }
+
+ @Override
+ public boolean accessProfileEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ @Override
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
+ }
+
+ @Override
+ public boolean isEnabled(BluetoothDevice device) {
+ if (mService == null || device == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
+ }
+
+ @Override
+ public int getConnectionPolicy(BluetoothDevice device) {
+ if (mService == null || device == null) {
+ return CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
+ }
+
+ @Override
+ public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+ boolean isEnabled = false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ if (enabled) {
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ }
+ } else {
+ isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ }
+
+ return isEnabled;
+ }
+
+ @Override
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ @Override
+ public int getProfileId() {
+ return BluetoothProfile.HAP_CLIENT;
+ }
+
+ @Override
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ @Override
+ public int getNameResource(BluetoothDevice device) {
+ return R.string.bluetooth_profile_hearing_aid;
+ }
+
+ @Override
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_hearing_aid_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_hearing_aid_profile_summary_connected;
+
+ default:
+ return BluetoothUtils.getConnectionStateSummary(state);
+ }
+ }
+
+ @Override
+ public int getDrawableResource(BluetoothClass btClass) {
+ return com.android.internal.R.drawable.ic_bt_hearing_aid;
+ }
+
+ /**
+ * Gets the name of this class
+ *
+ * @return the name of this class
+ */
+ public String toString() {
+ return NAME;
+ }
+
+ protected void finalize() {
+ Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mService);
+ mService = null;
+ } catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up HAP Client proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
index 51cf59c..ac9cdac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
@@ -34,16 +34,16 @@
private static final float CORNER_LINE_LENGTH = 264f; // 264dp
private static final float CORNER_RADIUS = 16f; // 16dp
- final private int mCornerColor;
- final private int mFocusedCornerColor;
- final private int mBackgroundColor;
+ private final int mCornerColor;
+ private final int mFocusedCornerColor;
+ private final int mBackgroundColor;
- final private Paint mStrokePaint;
- final private Paint mTransparentPaint;
- final private Paint mBackgroundPaint;
+ private final Paint mStrokePaint;
+ private final Paint mTransparentPaint;
+ private final Paint mBackgroundPaint;
- final private float mRadius;
- final private float mInnerRidus;
+ private final float mRadius;
+ private final float mInnerRadius;
private Bitmap mMaskBitmap;
private Canvas mMaskCanvas;
@@ -72,7 +72,7 @@
mRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS,
getResources().getDisplayMetrics());
// Inner radius needs to minus stroke width for keeping the width of border consistent.
- mInnerRidus = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ mInnerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
CORNER_RADIUS - CORNER_STROKE_WIDTH, getResources().getDisplayMetrics());
mCornerColor = context.getResources().getColor(R.color.qr_corner_line_color);
@@ -95,7 +95,10 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- if(mMaskBitmap == null) {
+ if (!isLaidOut()) {
+ return;
+ }
+ if (mMaskBitmap == null) {
mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
mMaskCanvas = new Canvas(mMaskBitmap);
}
@@ -105,16 +108,18 @@
@Override
protected void onDraw(Canvas canvas) {
- // Set frame line color.
- mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
- // Draw background color.
- mMaskCanvas.drawColor(mBackgroundColor);
- // Draw outer corner.
- mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
- // Draw inner transparent corner.
- mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRidus, mInnerRidus, mTransparentPaint);
+ if (mMaskCanvas != null && mMaskBitmap != null) {
+ // Set frame line color.
+ mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
+ // Draw background color.
+ mMaskCanvas.drawColor(mBackgroundColor);
+ // Draw outer corner.
+ mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
+ // Draw inner transparent corner.
+ mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRadius, mInnerRadius, mTransparentPaint);
- canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+ canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+ }
super.onDraw(canvas);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
new file mode 100644
index 0000000..03a792a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class HapClientProfileTest {
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private CachedBluetoothDeviceManager mDeviceManager;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private BluetoothDevice mBluetoothDevice;
+ @Mock
+ private BluetoothHapClient mService;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private BluetoothProfile.ServiceListener mServiceListener;
+ private HapClientProfile mProfile;
+
+ @Before
+ public void setUp() {
+ mProfile = new HapClientProfile(mContext, mDeviceManager, mProfileManager);
+ final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+ final ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(bluetoothManager.getAdapter());
+ mServiceListener = shadowBluetoothAdapter.getServiceListener();
+ }
+
+ @Test
+ public void onServiceConnected_isProfileReady() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+ assertThat(mProfile.isProfileReady()).isTrue();
+ verify(mProfileManager).callServiceConnectedListeners();
+ }
+
+ @Test
+ public void onServiceDisconnected_isProfileNotReady() {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HAP_CLIENT);
+
+ assertThat(mProfile.isProfileReady()).isFalse();
+ verify(mProfileManager).callServiceDisconnectedListeners();
+ }
+
+ @Test
+ public void getConnectionStatus_returnCorrectConnectionState() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionState(mBluetoothDevice))
+ .thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+ assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
+ .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void isEnabled_connectionPolicyAllowed_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+ assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
+ }
+
+ @Test
+ public void isEnabled_connectionPolicyForbidden_returnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+
+ assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
+ }
+
+ @Test
+ public void getConnectionPolicy_returnCorrectConnectionPolicy() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+ assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
+ .isEqualTo(CONNECTION_POLICY_ALLOWED);
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+ }
+
+ @Test
+ public void getConnectedDevices_returnCorrectList() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ int[] connectedStates = new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING};
+ List<BluetoothDevice> connectedList = Arrays.asList(
+ mBluetoothDevice,
+ mBluetoothDevice,
+ mBluetoothDevice);
+ when(mService.getDevicesMatchingConnectionStates(connectedStates))
+ .thenReturn(connectedList);
+
+ assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
+ }
+
+ @Test
+ public void getConnectableDevices_returnCorrectList() {
+ mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+ int[] connectableStates = new int[] {
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING};
+ List<BluetoothDevice> connectableList = Arrays.asList(
+ mBluetoothDevice,
+ mBluetoothDevice,
+ mBluetoothDevice,
+ mBluetoothDevice);
+ when(mService.getDevicesMatchingConnectionStates(connectableStates))
+ .thenReturn(connectableList);
+
+ assertThat(mProfile.getConnectableDevices().size()).isEqualTo(connectableList.size());
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index def7ddc..98af15a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -122,6 +122,7 @@
Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cde4bc4..80af69c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -177,6 +177,7 @@
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
index e34f5c8..d866653 100644
--- a/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
+++ b/packages/Shell/tests/src/com/android/shell/ActionSendMultipleConsumerActivity.java
@@ -104,7 +104,7 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(CUSTOM_ACTION_SEND_MULTIPLE_INTENT);
- context.registerReceiver(receiver, filter);
+ context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
}
/**
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 753e110..7a4a93f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -125,6 +125,7 @@
"jsr330",
"lottie",
"LowLightDreamLib",
+ "motion_tool_lib",
],
manifest: "AndroidManifest.xml",
@@ -230,6 +231,7 @@
"jsr330",
"WindowManager-Shell",
"LowLightDreamLib",
+ "motion_tool_lib",
],
libs: [
"android.test.runner",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4267ba2..07e37d3 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -557,6 +557,16 @@
android:showForAllUsers="true">
</activity>
+ <!-- started from SensoryPrivacyService -->
+ <activity android:name=".sensorprivacy.television.TvSensorPrivacyChangedActivity"
+ android:exported="true"
+ android:launchMode="singleTop"
+ android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
+ android:theme="@style/BottomSheet"
+ android:finishOnCloseSystemDialogs="true"
+ android:showForAllUsers="true">
+ </activity>
+
<!-- started from UsbDeviceSettingsManager -->
<activity android:name=".usb.UsbAccessoryUriActivity"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index c50340c..e52a57f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -82,6 +82,18 @@
boolean isFalseTap(@Penalty int penalty);
/**
+ * Returns true if the FalsingManager thinks the last gesture was not a valid long tap.
+ *
+ * Use this method to validate a long tap for launching an action, like long press on a UMO
+ *
+ * The only parameter, penalty, indicates how much this should affect future gesture
+ * classifications if this long tap looks like a false.
+ * As long taps are hard to confirm as false or otherwise,
+ * a low penalty value is encouraged unless context indicates otherwise.
+ */
+ boolean isFalseLongTap(@Penalty int penalty);
+
+ /**
* Returns true if the last two gestures do not look like a double tap.
*
* Only works on data that has already been reported to the FalsingManager. Be sure that
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 006b260..9add32c 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -18,6 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/dream_overlay_status_bar"
+ android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="@dimen/dream_overlay_status_bar_height"
android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
diff --git a/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
new file mode 100644
index 0000000..aab914f
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+ such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+ to wide devices that are shorter in height, like foldables. -->
+<resources>
+ <!-- Space between status view and notification shelf -->
+ <dimen name="keyguard_status_view_bottom_margin">35dp</dimen>
+ <dimen name="keyguard_clock_top_margin">40dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 347cf29..d9df337 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -17,8 +17,6 @@
<resources>
<dimen name="notification_panel_margin_horizontal">48dp</dimen>
<dimen name="status_view_margin_horizontal">62dp</dimen>
- <dimen name="keyguard_clock_top_margin">40dp</dimen>
- <dimen name="keyguard_status_view_bottom_margin">40dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
<!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
new file mode 100644
index 0000000..97ead01
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+ such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+ to wide devices that are shorter in height, like foldables. -->
+<resources>
+ <!-- Space between status view and notification shelf -->
+ <dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
+ <dimen name="keyguard_clock_top_margin">80dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 3d8da8a..17f82b5 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,8 +21,6 @@
for different hardware and product builds. -->
<resources>
<dimen name="status_view_margin_horizontal">124dp</dimen>
- <dimen name="keyguard_clock_top_margin">80dp</dimen>
- <dimen name="keyguard_status_view_bottom_margin">80dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">200dp</dimen>
<dimen name="large_screen_shade_header_left_padding">24dp</dimen>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 93982cb..ce9829b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -743,6 +743,17 @@
<integer name="complicationRestoreMs">1000</integer>
+ <!-- Duration in milliseconds of the dream in un-blur animation. -->
+ <integer name="config_dreamOverlayInBlurDurationMs">249</integer>
+ <!-- Delay in milliseconds of the dream in un-blur animation. -->
+ <integer name="config_dreamOverlayInBlurDelayMs">133</integer>
+ <!-- Duration in milliseconds of the dream in complications fade-in animation. -->
+ <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer>
+ <!-- Delay in milliseconds of the dream in top complications fade-in animation. -->
+ <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer>
+ <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. -->
+ <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer>
+
<!-- Icons that don't show in a collapsed non-keyguard statusbar -->
<string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
<item>@*android:string/status_bar_volume</item>
@@ -783,4 +794,8 @@
<item>@color/dream_overlay_aqi_very_unhealthy</item>
<item>@color/dream_overlay_aqi_hazardous</item>
</integer-array>
+
+ <!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
+ is available. If false, UI will never show regardless of tethering availability" -->
+ <bool name="config_show_wifi_tethering">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b325c56..6a2d1f0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -783,6 +783,62 @@
Microphone and camera available
</string>
+ <!--- Title of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_turned_on_dialog_title">
+ Microphone turned on
+ </string>
+
+ <!--- Title of dialog triggered if the microphone sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_turned_off_dialog_title">
+ Microphone turned off
+ </string>
+
+ <!--- Content of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_unblocked_dialog_content">
+ Microphone is enabled for all apps and services.
+ </string>
+
+ <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+ and there are no active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_blocked_no_exception_dialog_content">
+ Microphone access is disabled for all apps and services.
+ You can enable microphone access in Settings > Privacy > Microphone.
+ </string>
+
+ <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+ and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_mic_blocked_with_exception_dialog_content">
+ Microphone access is disabled for all apps and services.
+ You can change this in Settings > Privacy > Microphone.
+ </string>
+
+ <!--- Title of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_camera_turned_on_dialog_title">
+ Camera turned on
+ </string>
+
+ <!--- Title of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_camera_turned_off_dialog_title">
+ Camera turned off
+ </string>
+
+ <!--- Content of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_camera_unblocked_dialog_content">
+ Camera is enabled for all apps and services.
+ </string>
+
+ <!--- Content of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_camera_blocked_dialog_content">
+ Camera access is disabled for all apps and services.
+ </string>
+
+ <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+ and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+ <string name="sensor_privacy_htt_blocked_dialog_content">To use the microphone button, enable microphone access in Settings.</string>
+
+ <!-- Sensor privacy dialog: Button to open system settings [CHAR LIMIT=50] -->
+ <string name="sensor_privacy_dialog_open_settings">Open settings.</string>
+
<!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
<string name="media_seamless_other_device">Other device</string>
@@ -989,6 +1045,21 @@
<!-- Title of the dialog that allows to select an app to share or record [CHAR LIMIT=NONE] -->
<string name="media_projection_permission_app_selector_title">Share or record an app</string>
+ <!-- Media projection permission dialog title when there is no app name (e.g. it could be a system service when casting). [CHAR LIMIT=100] -->
+ <string name="media_projection_permission_dialog_system_service_title">Allow this app to share or record?</string>
+
+ <!-- Media projection permission warning for capturing the whole screen when a system service requests it (e.g. when casting). [CHAR LIMIT=350] -->
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen">When you\'re sharing, recording, or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+ <!-- Media projection permission warning for capturing a single app when a system service requests it (e.g. when casting). [CHAR LIMIT=350] -->
+ <string name="media_projection_permission_dialog_system_service_warning_single_app">When you\'re sharing, recording, or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information.</string>
+
+ <!-- Title for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=100] -->
+ <string name="screen_capturing_disabled_by_policy_dialog_title">Blocked by your IT admin</string>
+
+ <!-- Description for the dialog that is shown when screen capturing is disabled by enterprise policy. [CHAR LIMIT=350] -->
+ <string name="screen_capturing_disabled_by_policy_dialog_description">Screen capturing is disabled by device policy</string>
+
<!-- The text to clear all notifications. [CHAR LIMIT=60] -->
<string name="clear_all_notifications_text">Clear all</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 8aa3aba..a14f971 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -291,8 +291,10 @@
}
private void endAnimations(String reason, boolean cancel) {
- Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
- Trace.endSection();
+ if (Trace.isEnabled()) {
+ Trace.instant(Trace.TRACE_TAG_APP,
+ "KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
+ }
mVisible = false;
mTmpArray.addAll(mRunningAnimations);
int size = mTmpArray.size();
@@ -502,20 +504,23 @@
@Override
public void onAnimationStart(Animator animation) {
- Trace.beginSection("KeyButtonRipple.start." + mName);
- Trace.endSection();
+ if (Trace.isEnabled()) {
+ Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.start." + mName);
+ }
}
@Override
public void onAnimationCancel(Animator animation) {
- Trace.beginSection("KeyButtonRipple.cancel." + mName);
- Trace.endSection();
+ if (Trace.isEnabled()) {
+ Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.cancel." + mName);
+ }
}
@Override
public void onAnimationEnd(Animator animation) {
- Trace.beginSection("KeyButtonRipple.end." + mName);
- Trace.endSection();
+ if (Trace.isEnabled()) {
+ Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.end." + mName);
+ }
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 3961438..ca780c8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -188,13 +188,10 @@
dozeFraction: Float,
foldFraction: Float,
) : ClockAnimations {
- private var foldState = AnimationState(0f)
- private var dozeState = AnimationState(0f)
+ private val dozeState = AnimationState(dozeFraction)
+ private val foldState = AnimationState(foldFraction)
init {
- dozeState = AnimationState(dozeFraction)
- foldState = AnimationState(foldFraction)
-
if (foldState.isActive) {
clocks.forEach { it.animateFoldAppear(false) }
} else {
@@ -235,7 +232,7 @@
private class AnimationState(
var fraction: Float,
) {
- var isActive: Boolean = fraction < 0.5f
+ var isActive: Boolean = fraction > 0.5f
fun update(newFraction: Float): Pair<Boolean, Boolean> {
if (newFraction == fraction) {
return Pair(isActive, false)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
similarity index 65%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
index f79ca10..2dc7a28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
@@ -12,17 +12,16 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
-package com.android.wm.shell.floating;
-
-import android.content.Intent;
+package com.android.systemui.shared.keyguard.shared.model
/**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * Collection of all supported "slots", placements where keyguard quick affordances can appear on
+ * the lock screen.
*/
-interface IFloatingTasks {
-
- void showTask(in Intent intent) = 1;
-
+object KeyguardQuickAffordanceSlots {
+ const val SLOT_ID_BOTTOM_START = "bottom_start"
+ const val SLOT_ID_BOTTOM_END = "bottom_end"
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index e743ec8..abefeba 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,6 +20,7 @@
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import com.android.systemui.shared.recents.ISystemUiProxy;
oneway interface IOverviewProxy {
@@ -44,12 +45,6 @@
void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8;
/**
- * Sent when there was an action on one of the onboarding tips view.
- * TODO: Move this implementation to SystemUI completely
- */
- void onTip(int actionType, int viewType) = 10;
-
- /**
* Sent when device assistant changes its default assistant whether it is available or not.
*/
void onAssistantAvailable(boolean available) = 13;
@@ -60,23 +55,11 @@
void onAssistantVisibilityChanged(float visibility) = 14;
/**
- * Sent when back is triggered.
- * TODO: Move this implementation to SystemUI completely
- */
- void onBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) = 15;
-
- /**
* Sent when some system ui state changes.
*/
void onSystemUiStateChanged(int stateFlags) = 16;
/**
- * Sent when the split screen is resized
- */
- void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17;
-
- /**
* Sent when suggested rotation button could be shown
*/
void onRotationProposal(int rotation, boolean isValid) = 18;
@@ -115,4 +98,9 @@
* Sent when split keyboard shortcut is triggered to enter stage split.
*/
void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+
+ /**
+ * Sent when the surface for navigation bar is created or changed
+ */
+ void onNavigationBarSurface(in SurfaceControl surface) = 26;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2b2b05ce..1c532fe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -24,7 +24,6 @@
import android.view.MotionEvent;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.RemoteTransitionCompat;
/**
* Temporary callbacks into SystemUI.
@@ -106,9 +105,6 @@
/** Sets home rotation enabled. */
void setHomeRotationEnabled(boolean enabled) = 45;
- /** Notifies that a swipe-up gesture has started */
- oneway void notifySwipeUpGestureStarted() = 46;
-
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
deleted file mode 100644
index 37e706a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TransitionOldType;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-
-import android.annotation.SuppressLint;
-import android.app.IApplicationThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-
-import com.android.wm.shell.util.CounterRotator;
-
-/**
- * @see RemoteAnimationAdapter
- */
-public class RemoteAnimationAdapterCompat {
-
- private final RemoteAnimationAdapter mWrapped;
- private final RemoteTransitionCompat mRemoteTransition;
-
- public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration,
- long statusBarTransitionDelay, IApplicationThread appThread) {
- mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration,
- statusBarTransitionDelay);
- mRemoteTransition = buildRemoteTransition(runner, appThread);
- }
-
- public RemoteAnimationAdapter getWrapped() {
- return mWrapped;
- }
-
- /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */
- public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner,
- IApplicationThread appThread) {
- return new RemoteTransitionCompat(
- new RemoteTransition(wrapRemoteTransition(runner), appThread));
- }
-
- public RemoteTransitionCompat getRemoteTransition() {
- return mRemoteTransition;
- }
-
- /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
- public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
- final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
- return new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- final Runnable animationFinishedCallback = new Runnable() {
- @Override
- public void run() {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
- + " finished callback", e);
- }
- }
- };
- remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers,
- nonApps, animationFinishedCallback);
- }
-
- @Override
- public void onAnimationCancelled(boolean isKeyguardOccluded) {
- remoteAnimationAdapter.onAnimationCancelled();
- }
- };
- }
-
- private static IRemoteTransition.Stub wrapRemoteTransition(
- final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
- return new IRemoteTransition.Stub() {
- final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
-
- @Override
- public void startAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t,
- IRemoteTransitionFinishedCallback finishCallback) {
- final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
- final RemoteAnimationTarget[] apps =
- RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
- final RemoteAnimationTarget[] wallpapers =
- RemoteAnimationTargetCompat.wrapNonApps(
- info, true /* wallpapers */, t, leashMap);
- final RemoteAnimationTarget[] nonApps =
- RemoteAnimationTargetCompat.wrapNonApps(
- info, false /* wallpapers */, t, leashMap);
-
- // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
- boolean isReturnToHome = false;
- TransitionInfo.Change launcherTask = null;
- TransitionInfo.Change wallpaper = null;
- int launcherLayer = 0;
- int rotateDelta = 0;
- float displayW = 0;
- float displayH = 0;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- // skip changes that we didn't wrap
- if (!leashMap.containsKey(change.getLeash())) continue;
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
- isReturnToHome = change.getMode() == TRANSIT_OPEN
- || change.getMode() == TRANSIT_TO_FRONT;
- launcherTask = change;
- launcherLayer = info.getChanges().size() - i;
- } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- wallpaper = change;
- }
- if (change.getParent() == null && change.getEndRotation() >= 0
- && change.getEndRotation() != change.getStartRotation()) {
- rotateDelta = change.getEndRotation() - change.getStartRotation();
- displayW = change.getEndAbsBounds().width();
- displayH = change.getEndAbsBounds().height();
- }
- }
-
- // Prepare for rotation if there is one
- final CounterRotator counterLauncher = new CounterRotator();
- final CounterRotator counterWallpaper = new CounterRotator();
- if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
- counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
- rotateDelta, displayW, displayH);
- if (counterLauncher.getSurface() != null) {
- t.setLayer(counterLauncher.getSurface(), launcherLayer);
- }
- }
-
- if (isReturnToHome) {
- if (counterLauncher.getSurface() != null) {
- t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
- }
- // Need to "boost" the closing things since that's what launcher expects.
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final SurfaceControl leash = leashMap.get(change.getLeash());
- // skip changes that we didn't wrap
- if (leash == null) continue;
- final int mode = info.getChanges().get(i).getMode();
- // Only deal with independent layers
- if (!TransitionInfo.isIndependent(change, info)) continue;
- if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- t.setLayer(leash, info.getChanges().size() * 3 - i);
- counterLauncher.addChild(t, leash);
- }
- }
- // Make wallpaper visible immediately since launcher apparently won't do this.
- for (int i = wallpapers.length - 1; i >= 0; --i) {
- t.show(wallpapers[i].leash);
- t.setAlpha(wallpapers[i].leash, 1.f);
- }
- } else {
- if (launcherTask != null) {
- counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
- }
- if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
- counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
- rotateDelta, displayW, displayH);
- if (counterWallpaper.getSurface() != null) {
- t.setLayer(counterWallpaper.getSurface(), -1);
- counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
- }
- }
- }
- t.apply();
-
- final Runnable animationFinishedCallback = new Runnable() {
- @Override
- @SuppressLint("NewApi")
- public void run() {
- final SurfaceControl.Transaction finishTransaction =
- new SurfaceControl.Transaction();
- counterLauncher.cleanUp(finishTransaction);
- counterWallpaper.cleanUp(finishTransaction);
- // Release surface references now. This is apparently to free GPU memory
- // while doing quick operations (eg. during CTS).
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- info.getChanges().get(i).getLeash().release();
- }
- // Don't release here since launcher might still be using them. Instead
- // let launcher release them (eg. via RemoteAnimationTargets)
- leashMap.clear();
- try {
- finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
- } catch (RemoteException e) {
- Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
- + " finished callback", e);
- }
- }
- };
- synchronized (mFinishRunnables) {
- mFinishRunnables.put(token, animationFinishedCallback);
- }
- // TODO(bc-unlcok): Pass correct transit type.
- remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
- apps, wallpapers, nonApps, () -> {
- synchronized (mFinishRunnables) {
- if (mFinishRunnables.remove(token) == null) return;
- }
- animationFinishedCallback.run();
- });
- }
-
- @Override
- public void mergeAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) {
- // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
- // to legacy cancel.
- final Runnable finishRunnable;
- synchronized (mFinishRunnables) {
- finishRunnable = mFinishRunnables.remove(mergeTarget);
- }
- if (finishRunnable == null) return;
- remoteAnimationAdapter.onAnimationCancelled();
- finishRunnable.run();
- }
- };
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
deleted file mode 100644
index ab55037..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.view.RemoteAnimationDefinition;
-
-/**
- * @see RemoteAnimationDefinition
- */
-public class RemoteAnimationDefinitionCompat {
-
- private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition();
-
- public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) {
- mWrapped.addRemoteAnimation(transition, adapter.getWrapped());
- }
-
- public void addRemoteAnimation(int transition, int activityTypeFilter,
- RemoteAnimationAdapterCompat adapter) {
- mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped());
- }
-
- public RemoteAnimationDefinition getWrapped() {
- return mWrapped;
- }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5809c81..93c8073 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,12 +16,197 @@
package com.android.systemui.shared.system;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-public interface RemoteAnimationRunnerCompat {
- void onAnimationStart(@WindowManager.TransitionOldType int transit,
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManager.TransitionOldType;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.util.CounterRotator;
+
+public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
+
+ public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
- void onAnimationCancelled();
+
+ @Override
+ public final void onAnimationStart(@TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+
+ onAnimationStart(transit, apps, wallpapers,
+ nonApps, () -> {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ + " finished callback", e);
+ }
+ });
+ }
+
+ public IRemoteTransition toRemoteTransition() {
+ return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
+ @Override
+ public void startAnimation(IBinder token, TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
+ final RemoteAnimationTarget[] apps =
+ RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
+ final RemoteAnimationTarget[] wallpapers =
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, true /* wallpapers */, t, leashMap);
+ final RemoteAnimationTarget[] nonApps =
+ RemoteAnimationTargetCompat.wrapNonApps(
+ info, false /* wallpapers */, t, leashMap);
+
+ // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
+ boolean isReturnToHome = false;
+ TransitionInfo.Change launcherTask = null;
+ TransitionInfo.Change wallpaper = null;
+ int launcherLayer = 0;
+ int rotateDelta = 0;
+ float displayW = 0;
+ float displayH = 0;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ // skip changes that we didn't wrap
+ if (!leashMap.containsKey(change.getLeash())) continue;
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+ isReturnToHome = change.getMode() == TRANSIT_OPEN
+ || change.getMode() == TRANSIT_TO_FRONT;
+ launcherTask = change;
+ launcherLayer = info.getChanges().size() - i;
+ } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ wallpaper = change;
+ }
+ if (change.getParent() == null && change.getEndRotation() >= 0
+ && change.getEndRotation() != change.getStartRotation()) {
+ rotateDelta = change.getEndRotation() - change.getStartRotation();
+ displayW = change.getEndAbsBounds().width();
+ displayH = change.getEndAbsBounds().height();
+ }
+ }
+
+ // Prepare for rotation if there is one
+ final CounterRotator counterLauncher = new CounterRotator();
+ final CounterRotator counterWallpaper = new CounterRotator();
+ if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
+ counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (counterLauncher.getSurface() != null) {
+ t.setLayer(counterLauncher.getSurface(), launcherLayer);
+ }
+ }
+
+ if (isReturnToHome) {
+ if (counterLauncher.getSurface() != null) {
+ t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
+ }
+ // Need to "boost" the closing things since that's what launcher expects.
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final SurfaceControl leash = leashMap.get(change.getLeash());
+ // skip changes that we didn't wrap
+ if (leash == null) continue;
+ final int mode = info.getChanges().get(i).getMode();
+ // Only deal with independent layers
+ if (!TransitionInfo.isIndependent(change, info)) continue;
+ if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+ t.setLayer(leash, info.getChanges().size() * 3 - i);
+ counterLauncher.addChild(t, leash);
+ }
+ }
+ // Make wallpaper visible immediately since launcher apparently won't do this.
+ for (int i = wallpapers.length - 1; i >= 0; --i) {
+ t.show(wallpapers[i].leash);
+ t.setAlpha(wallpapers[i].leash, 1.f);
+ }
+ } else {
+ if (launcherTask != null) {
+ counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
+ }
+ if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
+ counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
+ rotateDelta, displayW, displayH);
+ if (counterWallpaper.getSurface() != null) {
+ t.setLayer(counterWallpaper.getSurface(), -1);
+ counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
+ }
+ }
+ }
+ t.apply();
+
+ final Runnable animationFinishedCallback = () -> {
+ final SurfaceControl.Transaction finishTransaction =
+ new SurfaceControl.Transaction();
+ counterLauncher.cleanUp(finishTransaction);
+ counterWallpaper.cleanUp(finishTransaction);
+ // Release surface references now. This is apparently to free GPU memory
+ // while doing quick operations (eg. during CTS).
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ info.getChanges().get(i).getLeash().release();
+ }
+ // Don't release here since launcher might still be using them. Instead
+ // let launcher release them (eg. via RemoteAnimationTargets)
+ leashMap.clear();
+ try {
+ finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+ } catch (RemoteException e) {
+ Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ + " finished callback", e);
+ }
+ };
+ synchronized (mFinishRunnables) {
+ mFinishRunnables.put(token, animationFinishedCallback);
+ }
+ // TODO(bc-unlcok): Pass correct transit type.
+ onAnimationStart(TRANSIT_OLD_NONE,
+ apps, wallpapers, nonApps, () -> {
+ synchronized (mFinishRunnables) {
+ if (mFinishRunnables.remove(token) == null) return;
+ }
+ animationFinishedCallback.run();
+ });
+ }
+
+ @Override
+ public void mergeAnimation(IBinder token, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+ // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+ // to legacy cancel.
+ final Runnable finishRunnable;
+ synchronized (mFinishRunnables) {
+ finishRunnable = mFinishRunnables.remove(mergeTarget);
+ }
+ if (finishRunnable == null) return;
+ onAnimationCancelled(false /* isKeyguardOccluded */);
+ finishRunnable.run();
+ }
+ };
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d6655a7..d4d3d25 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -18,29 +18,22 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
-import android.content.ComponentName;
import android.graphics.Rect;
import android.os.IBinder;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -53,72 +46,23 @@
import android.window.PictureInPictureSurfaceTransaction;
import android.window.RemoteTransition;
import android.window.TaskSnapshot;
-import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DataClass;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.ArrayList;
-import java.util.concurrent.Executor;
/**
- * Wrapper to expose RemoteTransition (shell transitions) to Launcher.
- *
- * @see IRemoteTransition
- * @see TransitionFilter
+ * Helper class to build {@link RemoteTransition} objects
*/
-@DataClass
-public class RemoteTransitionCompat implements Parcelable {
+public class RemoteTransitionCompat {
private static final String TAG = "RemoteTransitionCompat";
- @NonNull final RemoteTransition mTransition;
- @Nullable TransitionFilter mFilter = null;
-
- RemoteTransitionCompat(RemoteTransition transition) {
- mTransition = transition;
- }
-
- public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner,
- @NonNull Executor executor, @Nullable IApplicationThread appThread) {
- IRemoteTransition remote = new IRemoteTransition.Stub() {
- @Override
- public void startAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t,
- IRemoteTransitionFinishedCallback finishedCallback) {
- final Runnable finishAdapter = () -> {
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to call transition finished callback", e);
- }
- };
- executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter));
- }
-
- @Override
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishedCallback) {
- final Runnable finishAdapter = () -> {
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to call transition finished callback", e);
- }
- };
- executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget,
- finishAdapter));
- }
- };
- mTransition = new RemoteTransition(remote, appThread);
- }
-
/** Constructor specifically for recents animation */
- public RemoteTransitionCompat(RecentsAnimationListener recents,
+ public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
RecentsAnimationControllerCompat controller, IApplicationThread appThread) {
IRemoteTransition remote = new IRemoteTransition.Stub() {
final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
@@ -193,25 +137,7 @@
mRecentsSession.commitTasksAppearedIfNeeded(recents);
}
};
- mTransition = new RemoteTransition(remote, appThread);
- }
-
- /** Adds a filter check that restricts this remote transition to home open transitions. */
- public void addHomeOpenCheck(ComponentName homeActivity) {
- if (mFilter == null) {
- mFilter = new TransitionFilter();
- }
- // No need to handle the transition that also dismisses keyguard.
- mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
- mFilter.mRequirements =
- new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
- new TransitionFilter.Requirement()};
- mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
- mFilter.mRequirements[0].mTopActivity = homeActivity;
- mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
- mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
- mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ return new RemoteTransition(remote, appThread);
}
/**
@@ -505,161 +431,4 @@
@Override public void animateNavigationBarToApp(long duration) {
}
}
-
-
-
- // Code below generated by codegen v1.0.23.
- //
- // DO NOT MODIFY!
- // CHECKSTYLE:OFF Generated code
- //
- // To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
- //
- // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
- // Settings > Editor > Code Style > Formatter Control
- //@formatter:off
-
-
- @DataClass.Generated.Member
- /* package-private */ RemoteTransitionCompat(
- @NonNull RemoteTransition transition,
- @Nullable TransitionFilter filter) {
- this.mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- this.mFilter = filter;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public @NonNull RemoteTransition getTransition() {
- return mTransition;
- }
-
- @DataClass.Generated.Member
- public @Nullable TransitionFilter getFilter() {
- return mFilter;
- }
-
- @Override
- @DataClass.Generated.Member
- public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- // You can override field parcelling by defining methods like:
- // void parcelFieldName(Parcel dest, int flags) { ... }
-
- byte flg = 0;
- if (mFilter != null) flg |= 0x2;
- dest.writeByte(flg);
- dest.writeTypedObject(mTransition, flags);
- if (mFilter != null) dest.writeTypedObject(mFilter, flags);
- }
-
- @Override
- @DataClass.Generated.Member
- public int describeContents() { return 0; }
-
- /** @hide */
- @SuppressWarnings({"unchecked", "RedundantCast"})
- @DataClass.Generated.Member
- protected RemoteTransitionCompat(@NonNull android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
- TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR);
-
- this.mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- this.mFilter = filter;
-
- // onConstructed(); // You can define this method to get a callback
- }
-
- @DataClass.Generated.Member
- public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR
- = new Parcelable.Creator<RemoteTransitionCompat>() {
- @Override
- public RemoteTransitionCompat[] newArray(int size) {
- return new RemoteTransitionCompat[size];
- }
-
- @Override
- public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) {
- return new RemoteTransitionCompat(in);
- }
- };
-
- /**
- * A builder for {@link RemoteTransitionCompat}
- */
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
- public static class Builder {
-
- private @NonNull RemoteTransition mTransition;
- private @Nullable TransitionFilter mFilter;
-
- private long mBuilderFieldsSet = 0L;
-
- public Builder(
- @NonNull RemoteTransition transition) {
- mTransition = transition;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mTransition);
- }
-
- @DataClass.Generated.Member
- public @NonNull Builder setTransition(@NonNull RemoteTransition value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mTransition = value;
- return this;
- }
-
- @DataClass.Generated.Member
- public @NonNull Builder setFilter(@NonNull TransitionFilter value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x2;
- mFilter = value;
- return this;
- }
-
- /** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull RemoteTransitionCompat build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x4; // Mark builder used
-
- if ((mBuilderFieldsSet & 0x2) == 0) {
- mFilter = null;
- }
- RemoteTransitionCompat o = new RemoteTransitionCompat(
- mTransition,
- mFilter);
- return o;
- }
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x4) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
- }
- }
-
- @DataClass.Generated(
- time = 1629321609807L,
- codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java",
- inputSignatures = "private static final java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate android.window.WindowContainerToken mPausingTask\nprivate android.window.WindowContainerToken mPipTask\nprivate android.window.TransitionInfo mInfo\nprivate android.view.SurfaceControl mOpeningLeash\nprivate android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate android.os.IBinder mTransition\n void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass")
- @Deprecated
- private void __metadata() {}
-
-
- //@formatter:on
- // End of generated code
-
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
deleted file mode 100644
index accc456..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.system;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-/** Interface for something that runs a remote transition animation. */
-public interface RemoteTransitionRunner {
- /**
- * Starts a transition animation. Once complete, the implementation should call
- * `finishCallback`.
- */
- void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
- Runnable finishCallback);
-
- /**
- * Attempts to merge a transition into the currently-running animation. If merge is not
- * possible/supported, this should do nothing. Otherwise, the implementation should call
- * `finishCallback` immediately to indicate that it merged the transition.
- *
- * @param transition The transition that wants to be merged into the running animation.
- * @param mergeTarget The transition to merge into (that this runner is currently animating).
- */
- default void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index d48d7ff..c9b8712 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -228,7 +228,7 @@
listenForDozing(this)
if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
listenForDozeAmountTransition(this)
- listenForGoneToAodTransition(this)
+ listenForAnyStateToAodTransition(this)
} else {
listenForDozeAmount(this)
}
@@ -286,10 +286,10 @@
* dozing.
*/
@VisibleForTesting
- internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
+ internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.goneToAodTransition.filter {
- it.transitionState == TransitionState.STARTED
+ keyguardTransitionInteractor.anyStateToAodTransition.filter {
+ it.transitionState == TransitionState.FINISHED
}.collect {
dozeAmount = 1f
clock?.animations?.doze(dozeAmount)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8..a0206f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -35,6 +35,7 @@
val keyguardOccluded: Boolean,
val occludingAppRequestingFp: Boolean,
val primaryUser: Boolean,
+ val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c756a17..2bb3a5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -106,8 +106,9 @@
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
- @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
+ @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
public @interface Mode {}
+ static final int MODE_UNINITIALIZED = -1;
static final int MODE_DEFAULT = 0;
static final int MODE_ONE_HANDED = 1;
static final int MODE_USER_SWITCHER = 2;
@@ -154,7 +155,11 @@
private boolean mDisappearAnimRunning;
private SwipeListener mSwipeListener;
private ViewMode mViewMode = new DefaultViewMode();
- private @Mode int mCurrentMode = MODE_DEFAULT;
+ /*
+ * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not
+ * yet been called on it. This will happen when the ViewController is initialized.
+ */
+ private @Mode int mCurrentMode = MODE_UNINITIALIZED;
private int mWidth = -1;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -347,6 +352,8 @@
private String modeToString(@Mode int mode) {
switch (mode) {
+ case MODE_UNINITIALIZED:
+ return "Uninitialized";
case MODE_DEFAULT:
return "Default";
case MODE_ONE_HANDED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 79a01b9..fbb114c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -318,6 +318,7 @@
@Override
public void onInit() {
mSecurityViewFlipperController.init();
+ configureMode();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 39dc609..9d2dcf0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -150,6 +150,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.settings.SecureSettings;
import com.google.android.collect.Lists;
@@ -321,17 +322,20 @@
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
+ private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
private final ContentObserver mTimeFormatChangeObserver;
private boolean mSwitchingUser;
private boolean mDeviceInteractive;
+ private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final TrustManager mTrustManager;
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final SecureSettings mSecureSettings;
private final InteractionJankMonitor mInteractionJankMonitor;
private final LatencyTracker mLatencyTracker;
private final StatusBarStateController mStatusBarStateController;
@@ -380,6 +384,7 @@
protected Handler getHandler() {
return mHandler;
}
+
private final Handler mHandler;
private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -704,6 +709,7 @@
/**
* Request to listen for face authentication when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request face auth listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFace()}
@@ -716,6 +722,7 @@
/**
* Request to listen for fingerprint when an app is occluding keyguard.
+ *
* @param request if true and mKeyguardOccluded, request fingerprint listening, else default
* to normal behavior.
* See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)}
@@ -1930,6 +1937,7 @@
Context context,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
+ SecureSettings secureSettings,
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
@@ -1972,6 +1980,7 @@
mStatusBarState = mStatusBarStateController.getState();
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
+ mSecureSettings = secureSettings;
dumpManager.registerDumpable(getClass().getName(), this);
mSensorPrivacyManager = sensorPrivacyManager;
mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2214,9 +2223,35 @@
Settings.System.TIME_12_24)));
}
};
+
mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(Settings.System.TIME_12_24),
false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+ updateSfpsRequireScreenOnToAuthPref();
+ mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSfpsRequireScreenOnToAuthPref();
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ mSecureSettings.getUriFor(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
+ false,
+ mSfpsRequireScreenOnToAuthPrefObserver,
+ getCurrentUser());
+ }
+
+ protected void updateSfpsRequireScreenOnToAuthPref() {
+ final int defaultSfpsRequireScreenOnToAuthValue =
+ mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+ mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
+ Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+ defaultSfpsRequireScreenOnToAuthValue,
+ getCurrentUser()) != 0;
}
private void initializeSimState() {
@@ -2261,6 +2296,22 @@
}
/**
+ * @return true if there's at least one sfps enrollment for the current user.
+ */
+ public boolean isSfpsEnrolled() {
+ return mAuthController.isSfpsEnrolled(getCurrentUser());
+ }
+
+ /**
+ * @return true if sfps HW is supported on this device. Can return true even if the user has
+ * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isSfpsSupported() {
+ return mAuthController.getSfpsProps() != null
+ && !mAuthController.getSfpsProps().isEmpty();
+ }
+
+ /**
* @return true if there's at least one face enrolled
*/
public boolean isFaceEnrolled() {
@@ -2583,13 +2634,21 @@
!(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
- && !isEncryptedOrLockdownForUser
- && userDoesNotHaveTrust);
+ && !isEncryptedOrLockdownForUser
+ && userDoesNotHaveTrust);
- final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+ boolean shouldListenSideFpsState = true;
+ if (isSfpsSupported() && isSfpsEnrolled()) {
+ shouldListenSideFpsState =
+ mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+ }
+
+ boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+ && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+ && shouldListenSideFpsState;
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
@@ -2611,6 +2670,7 @@
mKeyguardOccluded,
mOccludingAppRequestingFp,
mIsPrimaryUser,
+ shouldListenSideFpsState,
shouldListenForFingerprintAssistant,
mSwitchingUser,
isUdfps,
@@ -3712,6 +3772,11 @@
mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
}
+ if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mSfpsRequireScreenOnToAuthPrefObserver);
+ }
+
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
@@ -3784,6 +3849,13 @@
pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+ } else if (isSfpsSupported()) {
+ pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
+ pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
+ if (isSfpsEnrolled()) {
+ pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled="
+ + mSfpsRequireScreenOnToAuthPrefEnabled);
+ }
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 47ee71e..0e7deeb 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -269,7 +269,11 @@
}
private static void notifyBootCompleted(CoreStartable coreStartable) {
- Trace.beginSection(coreStartable.getClass().getSimpleName() + ".onBootCompleted()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP,
+ coreStartable.getClass().getSimpleName() + ".onBootCompleted()");
+ }
coreStartable.onBootCompleted();
Trace.endSection();
}
@@ -291,14 +295,18 @@
private static CoreStartable startAdditionalStartable(String clsName) {
CoreStartable startable;
if (DEBUG) Log.d(TAG, "loading: " + clsName);
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, clsName + ".newInstance()");
+ }
try {
- Trace.beginSection(clsName + ".newInstance()");
startable = (CoreStartable) Class.forName(clsName).newInstance();
- Trace.endSection();
} catch (ClassNotFoundException
| IllegalAccessException
| InstantiationException ex) {
throw new RuntimeException(ex);
+ } finally {
+ Trace.endSection();
}
return startStartable(startable);
@@ -306,7 +314,10 @@
private static CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
if (DEBUG) Log.d(TAG, "loading: " + clsName);
- Trace.beginSection("Provider<" + clsName + ">.get()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, "Provider<" + clsName + ">.get()");
+ }
CoreStartable startable = provider.get();
Trace.endSection();
return startStartable(startable);
@@ -314,7 +325,10 @@
private static CoreStartable startStartable(CoreStartable startable) {
if (DEBUG) Log.d(TAG, "running: " + startable);
- Trace.beginSection(startable.getClass().getSimpleName() + ".start()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, startable.getClass().getSimpleName() + ".start()");
+ }
startable.start();
Trace.endSection();
@@ -355,15 +369,22 @@
public void onConfigurationChanged(Configuration newConfig) {
if (mServicesStarted) {
ConfigurationController configController = mSysUIComponent.getConfigurationController();
- Trace.beginSection(
- configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP,
+ configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+ }
configController.onConfigurationChanged(newConfig);
Trace.endSection();
int len = mServices.length;
for (int i = 0; i < len; i++) {
if (mServices[i] != null) {
- Trace.beginSection(
- mServices[i].getClass().getSimpleName() + ".onConfigurationChanged()");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP,
+ mServices[i].getClass().getSimpleName()
+ + ".onConfigurationChanged()");
+ }
mServices[i].onConfigurationChanged(newConfig);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index a21f45f..632fcdc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -97,7 +97,6 @@
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
.setBackAnimation(mWMComponent.getBackAnimation())
- .setFloatingTasks(mWMComponent.getFloatingTasks())
.setDesktopMode(mWMComponent.getDesktopMode());
// Only initialize when not starting from tests since this currently initializes some
@@ -118,7 +117,6 @@
.setStartingSurface(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
.setBackAnimation(Optional.ofNullable(null))
- .setFloatingTasks(Optional.ofNullable(null))
.setDesktopMode(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 313ff4157..c7c7f86 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -52,7 +52,7 @@
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
@@ -147,7 +147,7 @@
@NonNull private final WindowManager mWindowManager;
@NonNull private final DisplayManager mDisplayManager;
@Nullable private UdfpsController mUdfpsController;
- @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
+ @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
@Nullable private SidefpsController mSidefpsController;
@Nullable private IBiometricContextListener mBiometricContextListener;
@Nullable private UdfpsLogger mUdfpsLogger;
@@ -160,6 +160,7 @@
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
@NonNull private final SparseBooleanArray mFaceEnrolledForUser;
+ @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -365,6 +366,15 @@
}
}
}
+ if (mSidefpsProps == null) {
+ Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
+ } else {
+ for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+ if (prop.sensorId == sensorId) {
+ mSfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged(modality);
}
@@ -722,6 +732,7 @@
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+ mSfpsEnrolledForUser = new SparseBooleanArray();
mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
@@ -799,17 +810,26 @@
private void updateUdfpsLocation() {
if (mUdfpsController != null) {
final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0);
+
final Rect previousUdfpsBounds = mUdfpsBounds;
mUdfpsBounds = udfpsProp.getLocation().getRect();
mUdfpsBounds.scale(mScaleFactor);
- mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
- new UdfpsOverlayParams(mUdfpsBounds, new Rect(
- 0, mCachedDisplayInfo.getNaturalHeight() / 2,
- mCachedDisplayInfo.getNaturalWidth(),
- mCachedDisplayInfo.getNaturalHeight()),
- mCachedDisplayInfo.getNaturalWidth(),
- mCachedDisplayInfo.getNaturalHeight(), mScaleFactor,
- mCachedDisplayInfo.rotation));
+
+ final Rect overlayBounds = new Rect(
+ 0, /* left */
+ mCachedDisplayInfo.getNaturalHeight() / 2, /* top */
+ mCachedDisplayInfo.getNaturalWidth(), /* right */
+ mCachedDisplayInfo.getNaturalHeight() /* botom */);
+
+ final UdfpsOverlayParams overlayParams = new UdfpsOverlayParams(
+ mUdfpsBounds,
+ overlayBounds,
+ mCachedDisplayInfo.getNaturalWidth(),
+ mCachedDisplayInfo.getNaturalHeight(),
+ mScaleFactor,
+ mCachedDisplayInfo.rotation);
+
+ mUdfpsController.updateOverlayParams(udfpsProp, overlayParams);
if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {
for (Callback cb : mCallbacks) {
cb.onUdfpsLocationChanged();
@@ -872,21 +892,22 @@
}
/**
- * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
+ * Stores the callback received from {@link com.android.server.display.DisplayModeDirector}.
*
- * DisplayModeDirector implements {@link IUdfpsHbmListener} and registers it with this class by
- * calling {@link CommandQueue#setUdfpsHbmListener(IUdfpsHbmListener)}.
+ * DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback}
+ * and registers it with this class by calling
+ * {@link CommandQueue#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)}.
*/
@Override
- public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
- mUdfpsHbmListener = listener;
+ public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
+ mUdfpsRefreshRateRequestCallback = callback;
}
/**
- * @return IUdfpsHbmListener that can be set by DisplayModeDirector.
+ * @return IUdfpsRefreshRateRequestCallback that can be set by DisplayModeDirector.
*/
- @Nullable public IUdfpsHbmListener getUdfpsHbmListener() {
- return mUdfpsHbmListener;
+ @Nullable public IUdfpsRefreshRateRequestCallback getUdfpsRefreshRateCallback() {
+ return mUdfpsRefreshRateRequestCallback;
}
@Override
@@ -964,6 +985,11 @@
return mUdfpsProps;
}
+ @Nullable
+ public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
+ return mSidefpsProps;
+ }
+
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
@@ -1090,6 +1116,17 @@
return mUdfpsEnrolledForUser.get(userId);
}
+ /**
+ * Whether the passed userId has enrolled SFPS.
+ */
+ public boolean isSfpsEnrolled(int userId) {
+ if (mSidefpsController == null) {
+ return false;
+ }
+
+ return mSfpsEnrolledForUser.get(userId);
+ }
+
/** If BiometricPrompt is currently being shown to the user. */
public boolean isShowing() {
return mCurrentDialog != null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 4ef1f92..3631057 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -23,16 +23,18 @@
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Point;
+import android.graphics.Rect;
import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.Handler;
@@ -50,10 +52,14 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.biometrics.dagger.BiometricsBackground;
import com.android.systemui.dagger.SysUISingleton;
@@ -78,6 +84,8 @@
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.time.SystemClock;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@@ -100,7 +108,7 @@
*/
@SuppressWarnings("deprecation")
@SysUISingleton
-public class UdfpsController implements DozeReceiver {
+public class UdfpsController implements DozeReceiver, Dumpable {
private static final String TAG = "UdfpsController";
private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
@@ -138,7 +146,7 @@
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
- @VisibleForTesting int mSensorId;
+ @VisibleForTesting @NonNull FingerprintSensorPropertiesInternal mSensorProps;
@VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
// TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -203,6 +211,11 @@
}
};
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mSensorProps=(" + mSensorProps + ")");
+ }
+
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@Override
public void showUdfpsOverlay(long requestId, int sensorId, int reason,
@@ -258,7 +271,7 @@
}
mAcquiredReceived = true;
final UdfpsView view = mOverlay.getOverlayView();
- if (view != null) {
+ if (view != null && isOptical()) {
unconfigureDisplay(view);
}
if (acquiredGood) {
@@ -299,17 +312,42 @@
mOverlay.getOverlayView().setDebugMessage(message);
});
}
+
+ public Rect getSensorBounds() {
+ return mOverlayParams.getSensorBounds();
+ }
+
+ /**
+ * Passes a mocked MotionEvent to OnTouch.
+ *
+ * @param event MotionEvent to simulate in onTouch
+ */
+ public void debugOnTouch(long requestId, MotionEvent event) {
+ UdfpsController.this.onTouch(requestId, event, false);
+ }
+
+ /**
+ * Debug to run onUiReady
+ */
+ public void debugOnUiReady(long requestId, int sensorId) {
+ if (UdfpsController.this.mAlternateTouchProvider != null) {
+ UdfpsController.this.mAlternateTouchProvider.onUiReady();
+ } else {
+ UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId);
+ }
+ }
}
/**
* Updates the overlay parameters and reconstructs or redraws the overlay, if necessary.
*
- * @param sensorId sensor for which the overlay is getting updated.
+ * @param sensorProps sensor for which the overlay is getting updated.
* @param overlayParams See {@link UdfpsOverlayParams}.
*/
- public void updateOverlayParams(int sensorId, @NonNull UdfpsOverlayParams overlayParams) {
- if (sensorId != mSensorId) {
- mSensorId = sensorId;
+ public void updateOverlayParams(@NonNull FingerprintSensorPropertiesInternal sensorProps,
+ @NonNull UdfpsOverlayParams overlayParams) {
+ if (mSensorProps.sensorId != sensorProps.sensorId) {
+ mSensorProps = sensorProps;
Log.w(TAG, "updateUdfpsParams | sensorId has changed");
}
@@ -333,7 +371,7 @@
mAuthControllerUpdateUdfpsLocation = r;
}
- public void setUdfpsDisplayMode(UdfpsDisplayModeProvider udfpsDisplayMode) {
+ public void setUdfpsDisplayMode(@NonNull UdfpsDisplayModeProvider udfpsDisplayMode) {
mUdfpsDisplayMode = udfpsDisplayMode;
}
@@ -437,7 +475,6 @@
}
final UdfpsView udfpsView = mOverlay.getOverlayView();
- final boolean isDisplayConfigured = udfpsView.isDisplayConfigured();
boolean handled = false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_OUTSIDE:
@@ -521,15 +558,14 @@
"minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
minor, major, v, exceedsVelocityThreshold);
final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
- if (!isDisplayConfigured && !mAcquiredReceived
- && !exceedsVelocityThreshold) {
+ if (!mOnFingerDown && !mAcquiredReceived && !exceedsVelocityThreshold) {
final float scale = mOverlayParams.getScaleFactor();
float scaledMinor = minor / scale;
float scaledMajor = major / scale;
-
onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor,
scaledMajor);
+
Log.v(TAG, "onTouch | finger down: " + touchInfo);
mTouchLogTime = mSystemClock.elapsedRealtime();
handled = true;
@@ -623,7 +659,7 @@
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
- @BiometricsBackground Executor biometricsExecutor,
+ @NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull BouncerInteractor bouncerInteractor) {
mContext = context;
mExecution = execution;
@@ -654,9 +690,19 @@
mLatencyTracker = latencyTracker;
mActivityLaunchAnimator = activityLaunchAnimator;
mAlternateTouchProvider = alternateTouchProvider.orElse(null);
+ mSensorProps = new FingerprintSensorPropertiesInternal(
+ -1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ new ArrayList<>() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+
mBiometricExecutor = biometricsExecutor;
mBouncerInteractor = bouncerInteractor;
+ mDumpManager.registerDumpable(TAG, this);
+
mOrientationListener = new BiometricDisplayListener(
context,
displayManager,
@@ -852,6 +898,10 @@
mIsAodInterruptActive = false;
}
+ private boolean isOptical() {
+ return mSensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+ }
+
public boolean isFingerDown() {
return mOnFingerDown;
}
@@ -868,7 +918,9 @@
+ " current: " + mOverlay.getRequestId());
return;
}
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+ if (isOptical()) {
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+ }
// Refresh screen timeout and boost process priority if possible.
mPowerManager.userActivity(mSystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
@@ -891,11 +943,11 @@
}
});
} else {
- mFingerprintManager.onPointerDown(requestId, mSensorId, x, y, minor, major);
+ mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major);
}
Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
final UdfpsView view = mOverlay.getOverlayView();
- if (view != null) {
+ if (view != null && isOptical()) {
view.configureDisplay(() -> {
if (mAlternateTouchProvider != null) {
mBiometricExecutor.execute(() -> {
@@ -903,7 +955,7 @@
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
});
} else {
- mFingerprintManager.onUiReady(requestId, mSensorId);
+ mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
}
});
@@ -929,15 +981,16 @@
}
});
} else {
- mFingerprintManager.onPointerUp(requestId, mSensorId);
+ mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId);
}
for (Callback cb : mCallbacks) {
cb.onFingerUp();
}
}
mOnFingerDown = false;
- unconfigureDisplay(view);
-
+ if (isOptical()) {
+ unconfigureDisplay(view);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
index b80b8a0..670a8e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
@@ -46,7 +46,7 @@
logger.e(TAG, "enable | already requested")
return
}
- if (authController.udfpsHbmListener == null) {
+ if (authController.udfpsRefreshRateCallback == null) {
logger.e(TAG, "enable | mDisplayManagerCallback is null")
return
}
@@ -60,7 +60,7 @@
try {
// This method is a misnomer. It has nothing to do with HBM, its purpose is to set
// the appropriate display refresh rate.
- authController.udfpsHbmListener!!.onHbmEnabled(request.displayId)
+ authController.udfpsRefreshRateCallback!!.onRequestEnabled(request.displayId)
logger.v(TAG, "enable | requested optimal refresh rate for UDFPS")
} catch (e: RemoteException) {
logger.e(TAG, "enable", e)
@@ -84,7 +84,7 @@
try {
// Allow DisplayManager to unset the UDFPS refresh rate.
- authController.udfpsHbmListener!!.onHbmDisabled(request.displayId)
+ authController.udfpsRefreshRateCallback!!.onRequestDisabled(request.displayId)
logger.v(TAG, "disable | removed the UDFPS refresh rate request")
} catch (e: RemoteException) {
logger.e(TAG, "disable", e)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index da50f1c..f48cfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics
import android.content.Context
+import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
@@ -27,6 +28,11 @@
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.util.Log
import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -36,6 +42,8 @@
private const val TAG = "UdfpsShell"
private const val REQUEST_ID = 2L
private const val SENSOR_ID = 0
+private const val MINOR = 10F
+private const val MAJOR = 10F
/**
* Used to show and hide the UDFPS overlay with statusbar commands.
@@ -67,6 +75,12 @@
hideUdfpsOverlay()
} else if (args.size == 2 && args[0] == "show") {
showOverlay(getEnrollmentReason(args[1]))
+ } else if (args.size == 1 && args[0] == "onUiReady") {
+ onUiReady()
+ } else if (args.size == 1 && args[0] == "simFingerDown") {
+ simFingerDown()
+ } else if (args.size == 1 && args[0] == "simFingerUp") {
+ simFingerUp()
} else {
invalidCommand(pw)
}
@@ -80,6 +94,11 @@
"auth-keyguard, auth-other, auth-settings]")
pw.println(" -> reason otherwise defaults to unknown")
pw.println(" - hide")
+ pw.println(" - onUiReady")
+ pw.println(" - simFingerDown")
+ pw.println(" -> Simulates onFingerDown on sensor")
+ pw.println(" - simFingerUp")
+ pw.println(" -> Simulates onFingerUp on sensor")
}
private fun invalidCommand(pw: PrintWriter) {
@@ -125,4 +144,54 @@
private fun hideOverlay() {
udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
}
+
+
+ @VisibleForTesting
+ fun onUiReady() {
+ udfpsOverlayController?.debugOnUiReady(REQUEST_ID, SENSOR_ID)
+ }
+
+ @VisibleForTesting
+ fun simFingerDown() {
+ val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
+
+ val downEvent: MotionEvent? = obtainMotionEvent(ACTION_DOWN, sensorBounds.exactCenterX(),
+ sensorBounds.exactCenterY(), MINOR, MAJOR)
+ udfpsOverlayController?.debugOnTouch(REQUEST_ID, downEvent)
+
+ val moveEvent: MotionEvent? = obtainMotionEvent(ACTION_MOVE, sensorBounds.exactCenterX(),
+ sensorBounds.exactCenterY(), MINOR, MAJOR)
+ udfpsOverlayController?.debugOnTouch(REQUEST_ID, moveEvent)
+
+ downEvent?.recycle()
+ moveEvent?.recycle()
+ }
+
+ @VisibleForTesting
+ fun simFingerUp() {
+ val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
+
+ val upEvent: MotionEvent? = obtainMotionEvent(ACTION_UP, sensorBounds.exactCenterX(),
+ sensorBounds.exactCenterY(), MINOR, MAJOR)
+ udfpsOverlayController?.debugOnTouch(REQUEST_ID, upEvent)
+ upEvent?.recycle()
+ }
+
+ private fun obtainMotionEvent(
+ action: Int,
+ x: Float,
+ y: Float,
+ minor: Float,
+ major: Float
+ ): MotionEvent? {
+ val pp = MotionEvent.PointerProperties()
+ pp.id = 1
+ val pc = MotionEvent.PointerCoords()
+ pc.x = x
+ pc.y = y
+ pc.touchMinor = minor
+ pc.touchMajor = major
+ return MotionEvent.obtain(0, 0, action, 1, arrayOf(pp), arrayOf(pc),
+ 0, 0, 1f, 1f, 0, 0, 0, 0)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 5850c95..08c7c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -127,7 +127,10 @@
action,
userId,
{
- Trace.beginSection("registerReceiver act=$action user=$userId")
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, "registerReceiver act=$action user=$userId")
+ }
context.registerReceiverAsUser(
this,
UserHandle.of(userId),
@@ -141,7 +144,11 @@
},
{
try {
- Trace.beginSection("unregisterReceiver act=$action user=$userId")
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP,
+ "unregisterReceiver act=$action user=$userId")
+ }
context.unregisterReceiver(this)
Trace.endSection()
logger.logContextReceiverUnregistered(userId, action)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 2245d84..beaccba 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -34,6 +34,8 @@
import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
import com.android.systemui.classifier.HistoryTracker.BeliefListener;
import com.android.systemui.dagger.qualifiers.TestHarness;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -65,6 +67,7 @@
private static final double FALSE_BELIEF_THRESHOLD = 0.9;
private final FalsingDataProvider mDataProvider;
+ private final LongTapClassifier mLongTapClassifier;
private final SingleTapClassifier mSingleTapClassifier;
private final DoubleTapClassifier mDoubleTapClassifier;
private final HistoryTracker mHistoryTracker;
@@ -73,6 +76,7 @@
private final boolean mTestHarness;
private final MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
+ private FeatureFlags mFeatureFlags;
private static final Queue<String> RECENT_INFO_LOG =
new ArrayDeque<>(RECENT_INFO_LOG_SIZE + 1);
private static final Queue<DebugSwipeRecord> RECENT_SWIPES =
@@ -175,19 +179,23 @@
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
MetricsLogger metricsLogger,
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
- SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
- HistoryTracker historyTracker, KeyguardStateController keyguardStateController,
+ SingleTapClassifier singleTapClassifier, LongTapClassifier longTapClassifier,
+ DoubleTapClassifier doubleTapClassifier, HistoryTracker historyTracker,
+ KeyguardStateController keyguardStateController,
AccessibilityManager accessibilityManager,
- @TestHarness boolean testHarness) {
+ @TestHarness boolean testHarness,
+ FeatureFlags featureFlags) {
mDataProvider = falsingDataProvider;
mMetricsLogger = metricsLogger;
mClassifiers = classifiers;
mSingleTapClassifier = singleTapClassifier;
+ mLongTapClassifier = longTapClassifier;
mDoubleTapClassifier = doubleTapClassifier;
mHistoryTracker = historyTracker;
mKeyguardStateController = keyguardStateController;
mAccessibilityManager = accessibilityManager;
mTestHarness = testHarness;
+ mFeatureFlags = featureFlags;
mDataProvider.addSessionListener(mSessionListener);
mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
@@ -313,6 +321,58 @@
}
@Override
+ public boolean isFalseLongTap(@Penalty int penalty) {
+ if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) {
+ return false;
+ }
+
+ checkDestroyed();
+
+ if (skipFalsing(GENERIC)) {
+ mPriorResults = getPassedResult(1);
+ logDebug("Skipped falsing");
+ return false;
+ }
+
+ double falsePenalty = 0;
+ switch(penalty) {
+ case NO_PENALTY:
+ falsePenalty = 0;
+ break;
+ case LOW_PENALTY:
+ falsePenalty = 0.1;
+ break;
+ case MODERATE_PENALTY:
+ falsePenalty = 0.3;
+ break;
+ case HIGH_PENALTY:
+ falsePenalty = 0.6;
+ break;
+ }
+
+ FalsingClassifier.Result longTapResult =
+ mLongTapClassifier.isTap(mDataProvider.getRecentMotionEvents().isEmpty()
+ ? mDataProvider.getPriorMotionEvents()
+ : mDataProvider.getRecentMotionEvents(), falsePenalty);
+ mPriorResults = Collections.singleton(longTapResult);
+
+ if (!longTapResult.isFalse()) {
+ if (mDataProvider.isJustUnlockedWithFace()) {
+ // Immediately pass if a face is detected.
+ mPriorResults = getPassedResult(1);
+ logDebug("False Long Tap: false (face detected)");
+ } else {
+ mPriorResults = getPassedResult(0.1);
+ logDebug("False Long Tap: false (default)");
+ }
+ return false;
+ } else {
+ logDebug("False Long Tap: " + longTapResult.isFalse() + " (simple)");
+ return longTapResult.isFalse();
+ }
+ }
+
+ @Override
public boolean isFalseDoubleTap() {
checkDestroyed();
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 5d04b5f..c4723e8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -139,6 +139,11 @@
}
@Override
+ public boolean isFalseLongTap(int penalty) {
+ return mInternalFalsingManager.isFalseLongTap(penalty);
+ }
+
+ @Override
public boolean isFalseDoubleTap() {
return mInternalFalsingManager.isFalseDoubleTap();
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index 7b7f17e..5302af9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -40,6 +40,7 @@
public interface FalsingModule {
String BRIGHT_LINE_GESTURE_CLASSIFERS = "bright_line_gesture_classifiers";
String SINGLE_TAP_TOUCH_SLOP = "falsing_single_tap_touch_slop";
+ String LONG_TAP_TOUCH_SLOP = "falsing_long_tap_slop";
String DOUBLE_TAP_TOUCH_SLOP = "falsing_double_tap_touch_slop";
String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms";
@@ -81,4 +82,11 @@
static float providesSingleTapTouchSlop(ViewConfiguration viewConfiguration) {
return viewConfiguration.getScaledTouchSlop();
}
+
+ /** */
+ @Provides
+ @Named(LONG_TAP_TOUCH_SLOP)
+ static float providesLongTapTouchSlop(ViewConfiguration viewConfiguration) {
+ return viewConfiguration.getScaledTouchSlop() * 1.25f;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java
new file mode 100644
index 0000000..1963e69
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/LongTapClassifier.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.classifier;
+
+import static com.android.systemui.classifier.FalsingModule.LONG_TAP_TOUCH_SLOP;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * Falsing classifier that accepts or rejects a gesture as a long tap.
+ */
+public class LongTapClassifier extends TapClassifier{
+
+ @Inject
+ LongTapClassifier(FalsingDataProvider dataProvider,
+ @Named(LONG_TAP_TOUCH_SLOP) float touchSlop) {
+ super(dataProvider, touchSlop);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
index bd6fbfb..7a7401d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/SingleTapClassifier.java
@@ -18,57 +18,17 @@
import static com.android.systemui.classifier.FalsingModule.SINGLE_TAP_TOUCH_SLOP;
-import android.view.MotionEvent;
-
-import java.util.List;
-
import javax.inject.Inject;
import javax.inject.Named;
/**
- * Falsing classifier that accepts or rejects a single gesture as a tap.
+ * Falsing classifier that accepts or rejects a gesture as a single tap.
*/
-public class SingleTapClassifier extends FalsingClassifier {
- private final float mTouchSlop;
+public class SingleTapClassifier extends TapClassifier {
@Inject
SingleTapClassifier(FalsingDataProvider dataProvider,
@Named(SINGLE_TAP_TOUCH_SLOP) float touchSlop) {
- super(dataProvider);
- mTouchSlop = touchSlop;
- }
-
- @Override
- Result calculateFalsingResult(
- @Classifier.InteractionType int interactionType,
- double historyBelief, double historyConfidence) {
- return isTap(getRecentMotionEvents(), 0.5);
- }
-
- /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
- public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) {
- if (motionEvents.isEmpty()) {
- return falsed(0, "no motion events");
- }
- float downX = motionEvents.get(0).getX();
- float downY = motionEvents.get(0).getY();
-
- for (MotionEvent event : motionEvents) {
- String reason;
- if (Math.abs(event.getX() - downX) >= mTouchSlop) {
- reason = "dX too big for a tap: "
- + Math.abs(event.getX() - downX)
- + "vs "
- + mTouchSlop;
- return falsed(falsePenalty, reason);
- } else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
- reason = "dY too big for a tap: "
- + Math.abs(event.getY() - downY)
- + " vs "
- + mTouchSlop;
- return falsed(falsePenalty, reason);
- }
- }
- return Result.passed(0);
+ super(dataProvider, touchSlop);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java
new file mode 100644
index 0000000..e24cfaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TapClassifier.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.classifier;
+
+import android.view.MotionEvent;
+
+import java.util.List;
+
+/**
+ * Falsing classifier that accepts or rejects a gesture as a tap.
+ */
+public abstract class TapClassifier extends FalsingClassifier{
+ private final float mTouchSlop;
+
+ TapClassifier(FalsingDataProvider dataProvider,
+ float touchSlop) {
+ super(dataProvider);
+ mTouchSlop = touchSlop;
+ }
+
+ @Override
+ Result calculateFalsingResult(
+ @Classifier.InteractionType int interactionType,
+ double historyBelief, double historyConfidence) {
+ return isTap(getRecentMotionEvents(), 0.5);
+ }
+
+ /** Given a list of {@link android.view.MotionEvent}'s, returns true if the look like a tap. */
+ public Result isTap(List<MotionEvent> motionEvents, double falsePenalty) {
+ if (motionEvents.isEmpty()) {
+ return falsed(0, "no motion events");
+ }
+ float downX = motionEvents.get(0).getX();
+ float downY = motionEvents.get(0).getY();
+
+ for (MotionEvent event : motionEvents) {
+ String reason;
+ if (Math.abs(event.getX() - downX) >= mTouchSlop) {
+ reason = "dX too big for a tap: "
+ + Math.abs(event.getX() - downX)
+ + "vs "
+ + mTouchSlop;
+ return falsed(falsePenalty, reason);
+ } else if (Math.abs(event.getY() - downY) >= mTouchSlop) {
+ reason = "dY too big for a tap: "
+ + Math.abs(event.getY() - downY)
+ + " vs "
+ + mTouchSlop;
+ return falsed(falsePenalty, reason);
+ }
+ }
+ return Result.passed(0);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index fb01691..4eb444e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -25,6 +25,7 @@
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.screenshot.LongScreenshotActivity;
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
+import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -142,4 +143,11 @@
@ClassKey(HdmiCecSetMenuLanguageActivity.class)
public abstract Activity bindHdmiCecSetMenuLanguageActivity(
HdmiCecSetMenuLanguageActivity activity);
+
+ /** Inject into TvSensorPrivacyChangedActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(TvSensorPrivacyChangedActivity.class)
+ public abstract Activity bindTvSensorPrivacyChangedActivity(
+ TvSensorPrivacyChangedActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 139a8b7..25418c3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.INotificationManager;
@@ -137,6 +138,12 @@
@Provides
@Singleton
+ static AppOpsManager provideAppOpsManager(Context context) {
+ return context.getSystemService(AppOpsManager.class);
+ }
+
+ @Provides
+ @Singleton
static AudioManager provideAudioManager(Context context) {
return context.getSystemService(AudioManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index d05bd51..c260b7d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -40,7 +40,6 @@
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
@@ -111,9 +110,6 @@
Builder setBackAnimation(Optional<BackAnimation> b);
@BindsInstance
- Builder setFloatingTasks(Optional<FloatingTasks> f);
-
- @BindsInstance
Builder setDesktopMode(Optional<DesktopMode> d);
SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 482bdaf..bcf5e7a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,12 +41,14 @@
import com.android.systemui.doze.dagger.DozeComponent;
import com.android.systemui.dreams.dagger.DreamModule;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagsModule;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.data.BouncerViewModule;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.motiontool.MotionToolModule;
import com.android.systemui.navigationbar.NavigationBarComponent;
import com.android.systemui.notetask.NoteTaskModule;
import com.android.systemui.people.PeopleModule;
@@ -133,6 +135,7 @@
FooterActionsModule.class,
LogModule.class,
MediaProjectionModule.class,
+ MotionToolModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,
@@ -238,6 +241,7 @@
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
+ FeatureFlags featureFlags,
@Main Executor sysuiMainExecutor) {
return Optional.ofNullable(BubblesManager.create(context,
bubblesOptional,
@@ -254,6 +258,7 @@
notifCollection,
notifPipeline,
sysUiState,
+ featureFlags,
sysuiMainExecutor));
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 096f969..d756f3a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -32,7 +32,6 @@
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
@@ -111,9 +110,6 @@
@WMSingleton
Optional<BackAnimation> getBackAnimation();
- @WMSingleton
- Optional<FloatingTasks> getFloatingTasks();
-
/**
* Optional {@link DesktopMode} component for interacting with desktop mode.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 60227ee..937884c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -171,7 +171,10 @@
@Override
public void onSensorChanged(SensorEvent event) {
- Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]);
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, "DozeScreenBrightness.onSensorChanged" + event.values[0]);
+ }
try {
if (mRegistered) {
mLastSensorValue = (int) event.values[0];
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
new file mode 100644
index 0000000..d8dd6a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.View
+import androidx.core.animation.doOnEnd
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.statusbar.BlurUtils
+import java.util.function.Consumer
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Controller for dream overlay animations. */
+class DreamOverlayAnimationsController
+@Inject
+constructor(
+ private val mBlurUtils: BlurUtils,
+ private val mComplicationHostViewController: ComplicationHostViewController,
+ private val mStatusBarViewController: DreamOverlayStatusBarViewController,
+ private val mOverlayStateController: DreamOverlayStateController,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
+ private val mDreamInBlurAnimDuration: Int,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+ @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+ private val mDreamInComplicationsAnimDuration: Int,
+ @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+ private val mDreamInTopComplicationsAnimDelay: Int,
+ @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+ private val mDreamInBottomComplicationsAnimDelay: Int
+) {
+
+ var mEntryAnimations: AnimatorSet? = null
+
+ /** Starts the dream content and dream overlay entry animations. */
+ fun startEntryAnimations(view: View) {
+ cancelRunningEntryAnimations()
+
+ mEntryAnimations = AnimatorSet()
+ mEntryAnimations?.apply {
+ playTogether(
+ buildDreamInBlurAnimator(view),
+ buildDreamInTopComplicationsAnimator(),
+ buildDreamInBottomComplicationsAnimator()
+ )
+ doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
+ start()
+ }
+ }
+
+ /** Cancels the dream content and dream overlay animations, if they're currently running. */
+ fun cancelRunningEntryAnimations() {
+ if (mEntryAnimations?.isRunning == true) {
+ mEntryAnimations?.cancel()
+ }
+ mEntryAnimations = null
+ }
+
+ private fun buildDreamInBlurAnimator(view: View): Animator {
+ return ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = mDreamInBlurAnimDuration.toLong()
+ startDelay = mDreamInBlurAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animator: ValueAnimator ->
+ mBlurUtils.applyBlur(
+ view.viewRootImpl,
+ mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
+ false /*opaque*/
+ )
+ }
+ }
+ }
+
+ private fun buildDreamInTopComplicationsAnimator(): Animator {
+ return ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = mDreamInComplicationsAnimDuration.toLong()
+ startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { va: ValueAnimator ->
+ setTopElementsAlpha(va.animatedValue as Float)
+ }
+ }
+ }
+
+ private fun buildDreamInBottomComplicationsAnimator(): Animator {
+ return ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = mDreamInComplicationsAnimDuration.toLong()
+ startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { va: ValueAnimator ->
+ setBottomElementsAlpha(va.animatedValue as Float)
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+ .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
+ }
+ }
+ )
+ }
+ }
+
+ /** Sets alpha of top complications and the status bar. */
+ private fun setTopElementsAlpha(alpha: Float) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
+ .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+ mStatusBarViewController.setAlpha(alpha)
+ }
+
+ /** Sets alpha of bottom complications. */
+ private fun setBottomElementsAlpha(alpha: Float) {
+ mComplicationHostViewController
+ .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+ .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+ }
+
+ private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
+ if (alpha > 0 && view.visibility != View.VISIBLE) {
+ view.visibility = View.VISIBLE
+ }
+
+ view.alpha = alpha
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 733a80d..d0d0184 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -54,6 +54,8 @@
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final BlurUtils mBlurUtils;
+ private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
+ private final DreamOverlayStateController mStateController;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -134,12 +136,16 @@
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
burnInProtectionUpdateInterval,
@Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
- BouncerCallbackInteractor bouncerCallbackInteractor) {
+ BouncerCallbackInteractor bouncerCallbackInteractor,
+ DreamOverlayAnimationsController animationsController,
+ DreamOverlayStateController stateController) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mBlurUtils = blurUtils;
+ mDreamOverlayAnimationsController = animationsController;
+ mStateController = stateController;
mComplicationHostViewController = complicationHostViewController;
mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
@@ -172,6 +178,11 @@
bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+
+ // Start dream entry animations. Skip animations for low light clock.
+ if (!mStateController.isLowLightActive()) {
+ mDreamOverlayAnimationsController.startEntryAnimations(mView);
+ }
}
@Override
@@ -182,6 +193,8 @@
bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+
+ mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
}
View getContainerView() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index d1b7368..8542412 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -90,13 +90,15 @@
new KeyguardUpdateMonitorCallback() {
@Override
public void onShadeExpandedChanged(boolean expanded) {
- if (mLifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED
- && mLifecycleRegistry.getCurrentState() != Lifecycle.State.STARTED) {
- return;
- }
+ mExecutor.execute(() -> {
+ if (getCurrentStateLocked() != Lifecycle.State.RESUMED
+ && getCurrentStateLocked() != Lifecycle.State.STARTED) {
+ return;
+ }
- mLifecycleRegistry.setCurrentState(
- expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+ setCurrentStateLocked(
+ expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+ });
}
};
@@ -146,29 +148,30 @@
() -> mExecutor.execute(DreamOverlayService.this::requestExit);
mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host);
mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry();
- setCurrentState(Lifecycle.State.CREATED);
- }
- private void setCurrentState(Lifecycle.State state) {
- mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state));
+ mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED));
}
@Override
public void onDestroy() {
mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
- setCurrentState(Lifecycle.State.DESTROYED);
- resetCurrentDreamOverlay();
+ mExecutor.execute(() -> {
+ setCurrentStateLocked(Lifecycle.State.DESTROYED);
- mDestroyed = true;
+ resetCurrentDreamOverlayLocked();
+
+ mDestroyed = true;
+ });
+
super.onDestroy();
}
@Override
public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
- setCurrentState(Lifecycle.State.STARTED);
-
mExecutor.execute(() -> {
+ setCurrentStateLocked(Lifecycle.State.STARTED);
+
mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
if (mDestroyed) {
@@ -181,7 +184,7 @@
// Reset the current dream overlay before starting a new one. This can happen
// when two dreams overlap (briefly, for a smoother dream transition) and both
// dreams are bound to the dream overlay service.
- resetCurrentDreamOverlay();
+ resetCurrentDreamOverlayLocked();
}
mDreamOverlayContainerViewController =
@@ -191,7 +194,7 @@
mStateController.setShouldShowComplications(shouldShowComplications());
addOverlayWindowLocked(layoutParams);
- setCurrentState(Lifecycle.State.RESUMED);
+ setCurrentStateLocked(Lifecycle.State.RESUMED);
mStateController.setOverlayActive(true);
final ComponentName dreamComponent = getDreamComponent();
mStateController.setLowLightActive(
@@ -202,6 +205,14 @@
});
}
+ private Lifecycle.State getCurrentStateLocked() {
+ return mLifecycleRegistry.getCurrentState();
+ }
+
+ private void setCurrentStateLocked(Lifecycle.State state) {
+ mLifecycleRegistry.setCurrentState(state);
+ }
+
/**
* Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
* called from the main executing thread. The window attributes closely mirror those that are
@@ -231,13 +242,13 @@
// Make extra sure the container view has been removed from its old parent (otherwise we
// risk an IllegalStateException in some cases when setting the container view as the
// window's content view and the container view hasn't been properly removed previously).
- removeContainerViewFromParent();
+ removeContainerViewFromParentLocked();
mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
}
- private void removeContainerViewFromParent() {
+ private void removeContainerViewFromParentLocked() {
View containerView = mDreamOverlayContainerViewController.getContainerView();
if (containerView == null) {
return;
@@ -250,13 +261,14 @@
parentView.removeView(containerView);
}
- private void resetCurrentDreamOverlay() {
+ private void resetCurrentDreamOverlayLocked() {
if (mStarted && mWindow != null) {
mWindowManager.removeView(mWindow.getDecorView());
}
mStateController.setOverlayActive(false);
mStateController.setLowLightActive(false);
+ mStateController.setEntryAnimationsFinished(false);
mDreamOverlayContainerViewController = null;
mDreamOverlayTouchMonitor = null;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 72feaca..e80d0be 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -51,6 +51,7 @@
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
+ public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -202,6 +203,14 @@
return containsState(STATE_LOW_LIGHT_ACTIVE);
}
+ /**
+ * Returns whether the dream content and dream overlay entry animations are finished.
+ * @return {@code true} if animations are finished, {@code false} otherwise.
+ */
+ public boolean areEntryAnimationsFinished() {
+ return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -218,7 +227,7 @@
}
if (existingState != mState) {
- notifyCallbacks(callback -> callback.onStateChanged());
+ notifyCallbacks(Callback::onStateChanged);
}
}
@@ -239,6 +248,15 @@
}
/**
+ * Sets whether dream content and dream overlay entry animations are finished.
+ * @param finished {@code true} if entry animations are finished, {@code false} otherwise.
+ */
+ public void setEntryAnimationsFinished(boolean finished) {
+ modifyState(finished ? OP_SET_STATE : OP_CLEAR_STATE,
+ STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index bb1c430..d17fbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -16,10 +16,6 @@
package com.android.systemui.dreams;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-
import android.app.AlarmManager;
import android.app.StatusBarManager;
import android.content.res.Resources;
@@ -83,6 +79,9 @@
private boolean mIsAttached;
+ // Whether dream entry animations are finished.
+ private boolean mEntryAnimationsFinished = false;
+
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -109,7 +108,9 @@
new DreamOverlayStateController.Callback() {
@Override
public void onStateChanged() {
- updateLowLightState();
+ mEntryAnimationsFinished =
+ mDreamOverlayStateController.areEntryAnimationsFinished();
+ updateVisibility();
}
};
@@ -195,7 +196,6 @@
mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
- updateLowLightState();
mTouchInsetSession.addViewToTracking(mView);
}
@@ -216,6 +216,26 @@
mIsAttached = false;
}
+ /**
+ * Sets alpha of the dream overlay status bar.
+ *
+ * No-op if the dream overlay status bar should not be shown.
+ */
+ protected void setAlpha(float alpha) {
+ updateVisibility();
+
+ if (mView.getVisibility() != View.VISIBLE) {
+ return;
+ }
+
+ mView.setAlpha(alpha);
+ }
+
+ private boolean shouldShowStatusBar() {
+ return !mDreamOverlayStateController.isLowLightActive()
+ && !mStatusBarWindowStateController.windowIsShowing();
+ }
+
private void updateWifiUnavailableStatusIcon() {
final NetworkCapabilities capabilities =
mConnectivityManager.getNetworkCapabilities(
@@ -235,13 +255,12 @@
hasAlarm ? buildAlarmContentDescription(alarm) : null);
}
- private void updateLowLightState() {
- int visibility = View.VISIBLE;
- if (mDreamOverlayStateController.isLowLightActive()
- || mStatusBarWindowStateController.windowIsShowing()) {
- visibility = View.INVISIBLE;
+ private void updateVisibility() {
+ if (shouldShowStatusBar()) {
+ mView.setVisibility(View.VISIBLE);
+ } else {
+ mView.setVisibility(View.INVISIBLE);
}
- mView.setVisibility(visibility);
}
private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
@@ -298,21 +317,11 @@
}
private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) {
- mMainExecutor.execute(() -> {
- if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) {
- return;
- }
+ if (!mIsAttached || !mEntryAnimationsFinished) {
+ return;
+ }
- switch (state) {
- case WINDOW_STATE_SHOWING:
- mView.setVisibility(View.INVISIBLE);
- break;
- case WINDOW_STATE_HIDING:
- case WINDOW_STATE_HIDDEN:
- mView.setVisibility(View.VISIBLE);
- break;
- }
- });
+ mMainExecutor.execute(this::updateVisibility);
}
private void onStatusBarItemsChanged(List<StatusBarItem> newItems) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index fd6cfc0..100ccc3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -28,6 +28,7 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.LifecycleOwner;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.util.ViewController;
import java.util.Collection;
@@ -49,20 +50,34 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ComplicationLayoutEngine mLayoutEngine;
+ private final DreamOverlayStateController mDreamOverlayStateController;
private final LifecycleOwner mLifecycleOwner;
private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+ // Whether dream entry animations are finished.
+ private boolean mEntryAnimationsFinished = false;
+
@Inject
protected ComplicationHostViewController(
@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
ComplicationLayoutEngine layoutEngine,
+ DreamOverlayStateController dreamOverlayStateController,
LifecycleOwner lifecycleOwner,
@Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
super(view);
mLayoutEngine = layoutEngine;
mLifecycleOwner = lifecycleOwner;
mComplicationCollectionViewModel = viewModel;
+ mDreamOverlayStateController = dreamOverlayStateController;
+
+ mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ mEntryAnimationsFinished =
+ mDreamOverlayStateController.areEntryAnimationsFinished();
+ }
+ });
}
@Override
@@ -123,6 +138,11 @@
final ComplicationId id = complication.getId();
final Complication.ViewHolder viewHolder = complication.getComplication()
.createView(complication);
+ // Complications to be added before dream entry animations are finished are set
+ // to invisible and are animated in.
+ if (!mEntryAnimationsFinished) {
+ viewHolder.getView().setVisibility(View.INVISIBLE);
+ }
mComplications.put(id, viewHolder);
if (viewHolder.getView().getParent() != null) {
Log.e(TAG, "View for complication "
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4fe1622..cb012fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -47,6 +47,14 @@
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
"burn_in_protection_update_interval";
public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter";
+ public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration";
+ public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay";
+ public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION =
+ "dream_in_complications_anim_duration";
+ public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY =
+ "dream_in_top_complications_anim_delay";
+ public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
+ "dream_in_bottom_complications_anim_delay";
/** */
@Provides
@@ -114,6 +122,51 @@
return resources.getInteger(R.integer.config_dreamOverlayMillisUntilFullJitter);
}
+ /**
+ * Duration in milliseconds of the dream in un-blur animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BLUR_ANIMATION_DURATION)
+ static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in un-blur animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
+ static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+ }
+
+ /**
+ * Duration in milliseconds of the dream in complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+ static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in top complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+ static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+ }
+
+ /**
+ * Delay in milliseconds of the dream in bottom complications fade-in animation.
+ */
+ @Provides
+ @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+ static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ }
+
@Provides
@DreamOverlayComponent.DreamOverlayScope
static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8bcf9f6..5c14f28 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -57,7 +57,7 @@
val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
// TODO(b/254512425): Tracking Bug
- val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true)
+ val NOTIFICATION_MEMORY_MONITOR_ENABLED = ReleasedFlag(112)
// TODO(b/254512731): Tracking Bug
@JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
@@ -89,7 +89,7 @@
* replacement of KeyguardBouncer.java.
*/
// TODO(b/254512385): Tracking Bug
- @JvmField val MODERN_BOUNCER = UnreleasedFlag(208)
+ @JvmField val MODERN_BOUNCER = ReleasedFlag(208)
/**
* Whether the user interactor and repository should use `UserSwitcherController`.
@@ -129,6 +129,14 @@
@JvmField val NEW_UDFPS_OVERLAY = UnreleasedFlag(215)
+ /**
+ * Whether to enable the code powering customizable lock screen quick affordances.
+ *
+ * Note that this flag does not enable individual implementations of quick affordances like the
+ * new camera quick affordance. Look for individual flags for those.
+ */
+ @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = UnreleasedFlag(216, teamfood = false)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = ReleasedFlag(300)
@@ -178,10 +186,18 @@
@Deprecated("Replaced by mobile and wifi specific flags.")
val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
+ // TODO(b/256614753): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606)
+ // TODO(b/256614210): Tracking Bug
val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607)
+ // TODO(b/256614751): Tracking Bug
+ val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = UnreleasedFlag(608)
+
+ // TODO(b/256613548): Tracking Bug
+ val NEW_STATUS_BAR_WIFI_ICON_BACKEND = UnreleasedFlag(609)
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = ReleasedFlag(700)
@@ -277,15 +293,6 @@
@JvmField
@Keep
- val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
-
- @JvmField
- @Keep
- val SHOW_FLOATING_TASKS_AS_BUBBLES =
- SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
-
- @JvmField
- @Keep
val ENABLE_FLING_TO_DISMISS_BUBBLE =
SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
@@ -299,6 +306,9 @@
val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
+ // TODO(b/256873975): Tracking Bug
+ @JvmField @Keep val WM_BUBBLE_BAR = UnreleasedFlag(1111)
+
// 1200 - predictive back
@JvmField
@Keep
@@ -337,7 +347,7 @@
@JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
// 1700 - clipboard
- @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true)
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
// 1800 - shade container
@@ -346,6 +356,12 @@
// 1900 - note task
@JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+ // 2000 - device controls
+ @Keep val USE_APP_PANELS = UnreleasedFlag(2000, true)
+
+ // 2100 - Falsing Manager
+ @JvmField val FALSING_FOR_LONG_TAPS = ReleasedFlag(2100)
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/flags/OWNERS b/packages/SystemUI/src/com/android/systemui/flags/OWNERS
new file mode 100644
index 0000000..c9d2db1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+
+# Bug component: 1203176
+
+mankoff@google.com # send reviews here
+
+pixel@google.com
+juliacr@google.com
+cinek@google.com
+alexflo@google.com
+dsandler@android.com
+adamcohen@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 41abb62..04a74ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -125,6 +125,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
@@ -266,6 +267,7 @@
private final Executor mUiBgExecutor;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
+ private final Lazy<ShadeController> mShadeController;
private boolean mSystemReady;
private boolean mBootCompleted;
@@ -1150,6 +1152,7 @@
ScreenOnCoordinator screenOnCoordinator,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
mContext = context;
@@ -1165,6 +1168,7 @@
mTrustManager = trustManager;
mUserSwitcherController = userSwitcherController;
mKeyguardDisplayManager = keyguardDisplayManager;
+ mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(getClass().getName(), this);
mDeviceConfig = deviceConfig;
mScreenOnCoordinator = screenOnCoordinator;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 546a409..450fa14 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -33,6 +33,8 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.widget.ImageView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
@@ -61,6 +63,7 @@
private UserManager mUserManager;
private PackageManager mPackageManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
@Inject
public WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager,
@@ -95,6 +98,10 @@
if (badgedIcon != null) {
((ImageView) findViewById(R.id.icon)).setImageDrawable(badgedIcon);
}
+
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ mBackCallback);
}
@VisibleForTesting
@@ -134,11 +141,16 @@
@Override
public void onDestroy() {
unregisterBroadcastReceiver();
+ getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
super.onDestroy();
}
@Override
public void onBackPressed() {
+ onBackInvoked();
+ }
+
+ private void onBackInvoked() {
// Ignore back presses.
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 56a1f1a..78a7c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,10 +42,12 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -71,6 +73,7 @@
KeyguardUserSwitcherComponent.class},
includes = {
FalsingModule.class,
+ KeyguardDataQuickAffordanceModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
StartKeyguardTransitionModule.class,
@@ -106,6 +109,7 @@
ScreenOnCoordinator screenOnCoordinator,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
return new KeyguardViewMediator(
@@ -133,6 +137,7 @@
screenOnCoordinator,
interactionJankMonitor,
dreamOverlayStateController,
+ shadeController,
notificationShadeWindowController,
activityLaunchAnimator);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 99ae85d..80c6130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -18,6 +18,7 @@
import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -45,4 +46,9 @@
fun dispatchBackKeyEventPreIme(): Boolean
fun showNextSecurityScreenOrFinish(): Boolean
fun resume()
+ fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?,
+ )
+ fun willDismissWithActions(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index c600e13..d6f521c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -53,6 +53,10 @@
override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ override val pickerName: String by lazy { context.getString(component.getTileTitleId()) }
+
+ override val pickerIconResourceId: Int by lazy { component.getTileImageId() }
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
new file mode 100644
index 0000000..bea9363
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+
+@Module
+object KeyguardDataQuickAffordanceModule {
+ @Provides
+ @ElementsIntoSet
+ fun quickAffordanceConfigs(
+ home: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ ): Set<KeyguardQuickAffordanceConfig> {
+ return setOf(
+ home,
+ quickAccessWallet,
+ qrCodeScanner,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 0a8090b..fd40d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -29,6 +29,10 @@
/** Unique identifier for this quick affordance. It must be globally unique. */
val key: String
+ val pickerName: String
+
+ val pickerIconResourceId: Int
+
/**
* The ever-changing state of the affordance.
*
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
new file mode 100644
index 0000000..9c9354f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?".
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
+
+ // TODO(b/254858695): implement a persistence layer (database).
+ private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+ /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
+ val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
+
+ /**
+ * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
+ * descending priority order.
+ */
+ suspend fun getSelections(): Map<String, List<String>> {
+ return _selections.value
+ }
+
+ /**
+ * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+ * IDs should be descending priority order.
+ */
+ suspend fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
+ // when we set its value to the same instance of the original map, even if we change the
+ // map by updating the value of one of its keys.
+ val copy = _selections.value.toMutableMap()
+ copy[slotId] = affordanceIds
+ _selections.value = copy
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index d620b2a..11f72ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Context
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -24,6 +25,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -34,11 +36,16 @@
class QrCodeScannerKeyguardQuickAffordanceConfig
@Inject
constructor(
+ @Application context: Context,
private val controller: QRCodeScannerController,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ override val pickerName = context.getString(R.string.qr_code_scanner_title)
+
+ override val pickerIconResourceId = R.drawable.ic_qr_code_scanner
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index be57a32..303e6a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Context
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -29,6 +30,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import javax.inject.Inject
@@ -40,12 +42,17 @@
class QuickAccessWalletKeyguardQuickAffordanceConfig
@Inject
constructor(
+ @Application context: Context,
private val walletController: QuickAccessWalletController,
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ override val pickerName = context.getString(R.string.accessibility_wallet_button)
+
+ override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
+
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 76b8f26..0046256 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -21,7 +21,6 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer
@@ -60,8 +59,6 @@
val hide = _hide.asStateFlow()
private val _startingToHide = MutableStateFlow(false)
val startingToHide = _startingToHide.asStateFlow()
- private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
- val onDismissAction = _onDismissAction.asStateFlow()
private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
val startingDisappearAnimation = _disappearAnimation.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
@@ -126,10 +123,6 @@
_startingToHide.value = startingToHide
}
- fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
- _onDismissAction.value = bouncerCallbackActionsModel
- }
-
fun setStartDisappearAnimation(runnable: Runnable?) {
_disappearAnimation.value = runnable
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..95f614f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Abstracts access to application state related to keyguard quick affordances. */
+@SysUISingleton
+class KeyguardQuickAffordanceRepository
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+ private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+) {
+ /**
+ * List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
+ * given ID. The configs are sorted in descending priority order.
+ */
+ val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
+ selectionManager.selections
+ .map { selectionsBySlotId ->
+ selectionsBySlotId.mapValues { (_, selections) ->
+ configs.filter { selections.contains(it.key) }
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = emptyMap(),
+ )
+
+ /**
+ * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
+ * slot with the given ID. The configs are sorted in descending priority order.
+ */
+ suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+ val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
+ return configs.filter { selections.contains(it.key) }
+ }
+
+ /**
+ * Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
+ * are sorted in descending priority order.
+ */
+ suspend fun getSelections(): Map<String, List<String>> {
+ return selectionManager.getSelections()
+ }
+
+ /**
+ * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+ * IDs should be descending priority order.
+ */
+ fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ scope.launch(backgroundDispatcher) {
+ selectionManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIds,
+ )
+ }
+ }
+
+ /**
+ * Returns the list of representation objects for all known affordances, regardless of what is
+ * selected. This is useful for building experiences like the picker/selector or user settings
+ * so the user can see everything that can be selected in a menu.
+ */
+ fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+ return configs.map { config ->
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config.key,
+ name = config.pickerName,
+ iconResourceId = config.pickerIconResourceId,
+ )
+ }
+ }
+
+ /**
+ * Returns the list of representation objects for all available slots on the keyguard. This is
+ * useful for building experiences like the picker/selector or user settings so the user can see
+ * each slot and select which affordance(s) is/are installed in each slot on the keyguard.
+ */
+ fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+ // TODO(b/256195304): source these from a config XML file.
+ return listOf(
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 6baaf5f..c867c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -23,9 +23,12 @@
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
@@ -65,6 +68,9 @@
*/
val isKeyguardShowing: Flow<Boolean>
+ /** Observable for whether the bouncer is showing. */
+ val isBouncerShowing: Flow<Boolean>
+
/**
* Observable for whether we are in doze state.
*
@@ -95,6 +101,9 @@
/** Observable for device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel>
+ /** Observable for biometric unlock modes */
+ val biometricUnlockState: Flow<BiometricUnlockModel>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -125,6 +134,7 @@
private val keyguardStateController: KeyguardStateController,
dozeHost: DozeHost,
wakefulnessLifecycle: WakefulnessLifecycle,
+ biometricUnlockController: BiometricUnlockController,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -159,6 +169,29 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onBouncerShowingChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isBouncerShowing,
+ TAG,
+ "updated isBouncerShowing"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isBouncerShowing,
+ TAG,
+ "initial isBouncerShowing"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+
override val isDozing: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
@@ -248,6 +281,24 @@
awaitClose { wakefulnessLifecycle.removeObserver(callback) }
}
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+ val callback =
+ object : BiometricUnlockController.BiometricModeListener {
+ override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+ trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+ }
+ }
+
+ biometricUnlockController.addBiometricModeListener(callback)
+ trySendWithFailureLogging(
+ biometricModeIntToObject(biometricUnlockController.getMode()),
+ TAG,
+ "initial biometric mode"
+ )
+
+ awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate
}
@@ -279,6 +330,20 @@
}
}
+ private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+ return when (value) {
+ 0 -> BiometricUnlockModel.NONE
+ 1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
+ 2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+ 3 -> BiometricUnlockModel.SHOW_BOUNCER
+ 4 -> BiometricUnlockModel.ONLY_WAKE
+ 5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
+ 6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ 7 -> BiometricUnlockModel.DISMISS_BOUNCER
+ else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..7e01db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodToGoneTransitionInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("AOD->GONE") {
+
+ private val wakeAndUnlockModes =
+ setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+ override fun start() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (biometricUnlockState, keyguardState) = pair
+ if (
+ keyguardState == KeyguardState.AOD &&
+ wakeAndUnlockModes.contains(biometricUnlockState)
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.AOD,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 500L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index e644347..dbb0352 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -30,7 +30,6 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -94,8 +93,6 @@
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
val startingDisappearAnimation: Flow<Runnable> =
repository.startingDisappearAnimation.filterNotNull()
- val onDismissAction: Flow<BouncerCallbackActionsModel> =
- repository.onDismissAction.filterNotNull()
val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
val keyguardPosition: Flow<Float> = repository.keyguardPosition
val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
@@ -160,7 +157,6 @@
}
keyguardStateController.notifyBouncerShowing(true)
callbackInteractor.dispatchStartingToShow()
-
Trace.endSection()
}
@@ -179,7 +175,6 @@
keyguardStateController.notifyBouncerShowing(false /* showing */)
cancelShowRunnable()
repository.setShowingSoon(false)
- repository.setOnDismissAction(null)
repository.setVisible(false)
repository.setHide(true)
repository.setShow(null)
@@ -241,7 +236,7 @@
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
- repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
}
/** Update the resources of the views. */
@@ -319,7 +314,7 @@
/** Return whether bouncer will dismiss with actions */
fun willDismissWithAction(): Boolean {
- return repository.onDismissAction.value?.onDismissAction != null
+ return bouncerView.delegate?.willDismissWithActions() == true
}
/** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 03c6a78..614ff8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -39,10 +41,19 @@
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
- /** Whether the keyguard is showing to not. */
+ /** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+ /** Whether the bouncer is showing or not. */
+ val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+ /** Observable for the [StatusBarState] */
+ val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /**
+ * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
+ * side, under display) is used to unlock the device.
+ */
+ val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 13d97aa..92caa89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -18,19 +18,30 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@SysUISingleton
@@ -43,7 +54,12 @@
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
+ private val featureFlags: FeatureFlags,
+ private val repository: Lazy<KeyguardQuickAffordanceRepository>,
) {
+ private val isUsingRepository: Boolean
+ get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+
/** Returns an observable for the quick affordance at the given position. */
fun quickAffordance(
position: KeyguardQuickAffordancePosition
@@ -72,7 +88,19 @@
configKey: String,
expandable: Expandable?,
) {
- @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
+ @Suppress("UNCHECKED_CAST")
+ val config =
+ if (isUsingRepository) {
+ val (slotId, decodedConfigKey) = configKey.decode()
+ repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
+ } else {
+ registry.get(configKey)
+ }
+ if (config == null) {
+ Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
+ return
+ }
+
when (val result = config.onTriggered(expandable)) {
is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
launchQuickAffordance(
@@ -84,28 +112,138 @@
}
}
+ /**
+ * Selects an affordance with the given ID on the slot with the given ID.
+ *
+ * @return `true` if the affordance was selected successfully; `false` otherwise.
+ */
+ suspend fun select(slotId: String, affordanceId: String): Boolean {
+ check(isUsingRepository)
+
+ val slots = repository.get().getSlotPickerRepresentations()
+ val slot = slots.find { it.id == slotId } ?: return false
+ val selections =
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ val alreadySelected = selections.remove(affordanceId)
+ if (!alreadySelected) {
+ while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
+ selections.removeAt(0)
+ }
+ }
+
+ selections.add(affordanceId)
+
+ repository
+ .get()
+ .setSelections(
+ slotId = slotId,
+ affordanceIds = selections,
+ )
+
+ return true
+ }
+
+ /**
+ * Unselects one or all affordances from the slot with the given ID.
+ *
+ * @param slotId The ID of the slot.
+ * @param affordanceId The ID of the affordance to remove; if `null`, removes all affordances
+ * from the slot.
+ * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
+ * the affordance was not on the slot to begin with).
+ */
+ suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
+ check(isUsingRepository)
+
+ val slots = repository.get().getSlotPickerRepresentations()
+ if (slots.find { it.id == slotId } == null) {
+ return false
+ }
+
+ if (affordanceId.isNullOrEmpty()) {
+ return if (
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
+ ) {
+ false
+ } else {
+ repository.get().setSelections(slotId = slotId, affordanceIds = emptyList())
+ true
+ }
+ }
+
+ val selections =
+ repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+ return if (selections.remove(affordanceId)) {
+ repository
+ .get()
+ .setSelections(
+ slotId = slotId,
+ affordanceIds = selections,
+ )
+ true
+ } else {
+ false
+ }
+ }
+
+ /** Returns affordance IDs indexed by slot ID, for all known slots. */
+ suspend fun getSelections(): Map<String, List<String>> {
+ check(isUsingRepository)
+
+ val selections = repository.get().getSelections()
+ return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
+ slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
+ }
+ }
+
private fun quickAffordanceInternal(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
- val configs = registry.getAll(position)
+ return if (isUsingRepository) {
+ repository
+ .get()
+ .selections
+ .map { it[position.toSlotId()] ?: emptyList() }
+ .flatMapLatest { configs -> combinedConfigs(position, configs) }
+ } else {
+ combinedConfigs(position, registry.getAll(position))
+ }
+ }
+
+ private fun combinedConfigs(
+ position: KeyguardQuickAffordancePosition,
+ configs: List<KeyguardQuickAffordanceConfig>,
+ ): Flow<KeyguardQuickAffordanceModel> {
+ if (configs.isEmpty()) {
+ return flowOf(KeyguardQuickAffordanceModel.Hidden)
+ }
+
return combine(
configs.map { config ->
- // We emit an initial "Hidden" value to make sure that there's always an initial
- // value and avoid subtle bugs where the downstream isn't receiving any values
- // because one config implementation is not emitting an initial value. For example,
- // see b/244296596.
+ // We emit an initial "Hidden" value to make sure that there's always an
+ // initial value and avoid subtle bugs where the downstream isn't receiving
+ // any values because one config implementation is not emitting an initial
+ // value. For example, see b/244296596.
config.lockScreenState.onStart {
emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
}
) { states ->
val index =
- states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible }
+ states.indexOfFirst { state ->
+ state is KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ }
if (index != -1) {
val visibleState =
states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ val configKey = configs[index].key
KeyguardQuickAffordanceModel.Visible(
- configKey = configs[index].key,
+ configKey =
+ if (isUsingRepository) {
+ configKey.encode(position.toSlotId())
+ } else {
+ configKey
+ },
icon = visibleState.icon,
activationState = visibleState.activationState,
)
@@ -145,4 +283,39 @@
)
}
}
+
+ private fun KeyguardQuickAffordancePosition.toSlotId(): String {
+ return when (this) {
+ KeyguardQuickAffordancePosition.BOTTOM_START ->
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ KeyguardQuickAffordancePosition.BOTTOM_END ->
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+ }
+ }
+
+ private fun String.encode(slotId: String): String {
+ return "$slotId$DELIMITER$this"
+ }
+
+ private fun String.decode(): Pair<String, String> {
+ val splitUp = this.split(DELIMITER)
+ return Pair(splitUp[0], splitUp[1])
+ }
+
+ fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+ check(isUsingRepository)
+
+ return repository.get().getAffordancePickerRepresentations()
+ }
+
+ fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+ check(isUsingRepository)
+
+ return repository.get().getSlotPickerRepresentations()
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceInteractor"
+ private const val DELIMITER = "::"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 83d9432..57fb4a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,11 +41,13 @@
}
scope.launch {
- interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) }
+ interactor.finishedKeyguardTransitionStep.collect {
+ logger.i("Finished transition", it)
+ }
}
scope.launch {
- interactor.startedKeyguardState.collect { logger.i("Started transition to", it) }
+ interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index d5ea77b..a7c6d44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -41,6 +41,7 @@
is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+ is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index dffd097..749183e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -21,7 +21,6 @@
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -44,8 +43,9 @@
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
- /** GONE->AOD information. */
- val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
+ /** (any)->AOD transition information */
+ val anyStateToAodTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == KeyguardState.AOD }
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -57,15 +57,15 @@
lockscreenToAodTransition,
)
+ /* The last [TransitionStep] with a [TransitionState] of FINISHED */
+ val finishedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
+
/* The last completed [KeyguardState] transition */
val finishedKeyguardState: Flow<KeyguardState> =
- repository.transitions
- .filter { step -> step.transitionState == TransitionState.FINISHED }
- .map { step -> step.to }
+ finishedKeyguardTransitionStep.map { step -> step.to }
- /* The last started [KeyguardState] transition */
- val startedKeyguardState: Flow<KeyguardState> =
- repository.transitions
- .filter { step -> step.transitionState == TransitionState.STARTED }
- .map { step -> step.to }
+ /* The last [TransitionStep] with a [TransitionState] of STARTED */
+ val startedKeyguardTransitionStep: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 761f3fd..fd4814d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -16,14 +16,16 @@
package com.android.systemui.keyguard.domain.interactor
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample
import java.util.UUID
@@ -38,7 +40,7 @@
@Inject
constructor(
@Application private val scope: CoroutineScope,
- private val keyguardRepository: KeyguardRepository,
+ private val keyguardInteractor: KeyguardInteractor,
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
@@ -47,12 +49,47 @@
private var transitionId: UUID? = null
override fun start() {
+ listenForDraggingUpToBouncer()
+ listenForBouncerHiding()
+ }
+
+ private fun listenForBouncerHiding() {
+ scope.launch {
+ keyguardInteractor.isBouncerShowing
+ .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
+ .collect { pair ->
+ val (isBouncerShowing, wakefulnessState) = pair
+ if (!isBouncerShowing) {
+ val to =
+ if (
+ wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
+ wakefulnessState == WakefulnessModel.ASLEEP
+ ) {
+ KeyguardState.AOD
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.BOUNCER,
+ to = to,
+ animator = getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
+ /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+ private fun listenForDraggingUpToBouncer() {
scope.launch {
shadeRepository.shadeModel
.sample(
combine(
keyguardTransitionInteractor.finishedKeyguardState,
- keyguardRepository.statusBarState,
+ keyguardInteractor.statusBarState,
) { keyguardState, statusBarState ->
Pair(keyguardState, statusBarState)
},
@@ -100,4 +137,15 @@
}
}
}
+
+ private fun getAnimator(): ValueAnimator {
+ return ValueAnimator().apply {
+ setInterpolator(Interpolators.LINEAR)
+ setDuration(TRANSITION_DURATION_MS)
+ }
+ }
+
+ companion object {
+ private const val TRANSITION_DURATION_MS = 300L
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 728bafa..37f33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -42,6 +42,8 @@
@Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
+ @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+
@Binds
@IntoSet
abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
new file mode 100644
index 0000000..db709b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Model device wakefulness states. */
+enum class BiometricUnlockModel {
+ /** Mode in which we don't need to wake up the device when we authenticate. */
+ NONE,
+ /**
+ * Mode in which we wake up the device, and directly dismiss Keyguard. Active when we acquire a
+ * fingerprint while the screen is off and the device was sleeping.
+ */
+ WAKE_AND_UNLOCK,
+ /**
+ * Mode in which we wake the device up, and fade out the Keyguard contents because they were
+ * already visible while pulsing in doze mode.
+ */
+ WAKE_AND_UNLOCK_PULSING,
+ /**
+ * Mode in which we wake up the device, but play the normal dismiss animation. Active when we
+ * acquire a fingerprint pulsing in doze mode.
+ */
+ SHOW_BOUNCER,
+ /**
+ * Mode in which we only wake up the device, and keyguard was not showing when we authenticated.
+ */
+ ONLY_WAKE,
+ /**
+ * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
+ * device while being requested when keyguard is occluded or showing.
+ */
+ UNLOCK_COLLAPSING,
+ /** When bouncer is visible and will be dismissed. */
+ DISMISS_BOUNCER,
+ /** Mode in which fingerprint wakes and unlocks the device from a dream. */
+ WAKE_AND_UNLOCK_FROM_DREAM,
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
similarity index 64%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index f79ca10..a56bc90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -12,17 +12,19 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
-package com.android.wm.shell.floating;
+package com.android.systemui.keyguard.shared.model
-import android.content.Intent;
+import androidx.annotation.DrawableRes
/**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * Representation of a quick affordance for use to build "picker", "selector", or "settings"
+ * experiences.
*/
-interface IFloatingTasks {
-
- void showTask(in Intent intent) = 1;
-
-}
+data class KeyguardQuickAffordancePickerRepresentation(
+ val id: String,
+ val name: String,
+ @DrawableRes val iconResourceId: Int,
+)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
similarity index 61%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
index f79ca10..86f2756 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
@@ -12,17 +12,17 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
+ *
*/
-package com.android.wm.shell.floating;
-
-import android.content.Intent;
+package com.android.systemui.keyguard.shared.model
/**
- * Interface that is exposed to remote callers to manipulate floating task features.
+ * Representation of a quick affordance slot (or position) for use to build "picker", "selector", or
+ * "settings" experiences.
*/
-interface IFloatingTasks {
-
- void showTask(in Intent intent) = 1;
-
-}
+data class KeyguardSlotPickerRepresentation(
+ val id: String,
+ /** The maximum number of selected affordances that can be present on this slot. */
+ val maxSelectedAffordances: Int = 1,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 0ca3582..732a6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -21,10 +21,11 @@
val to: KeyguardState = KeyguardState.NONE,
val value: Float = 0f, // constrained [0.0, 1.0]
val transitionState: TransitionState = TransitionState.FINISHED,
+ val ownerName: String = "",
) {
constructor(
info: TransitionInfo,
value: Float,
transitionState: TransitionState,
- ) : this(info.from, info.to, value, transitionState)
+ ) : this(info.from, info.to, value, transitionState, info.ownerName)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index df26014..a22958b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.collect
@@ -75,6 +76,17 @@
hostViewController.showPrimarySecurityScreen()
hostViewController.onResume()
}
+
+ override fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?
+ ) {
+ hostViewController.setOnDismissAction(onDismissAction, cancelAction)
+ }
+
+ override fun willDismissWithActions(): Boolean {
+ return hostViewController.hasDismissActions()
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -122,15 +134,6 @@
}
launch {
- viewModel.setDismissAction.collect {
- hostViewController.setOnDismissAction(
- it.onDismissAction,
- it.cancelAction
- )
- }
- }
-
- launch {
viewModel.startDisappearAnimation.collect {
hostViewController.startDisappearAnimation(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9b76a2c9..9a92843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,7 +20,6 @@
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
@@ -63,9 +62,6 @@
/** Observe whether bouncer is starting to hide. */
val startingToHide: Flow<Unit> = interactor.startingToHide
- /** Observe whether we want to set the dismiss action to the bouncer. */
- val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
-
/** Observe whether we want to start the disappear animation. */
val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index be357ee..ceb4845 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -79,8 +79,7 @@
val queryIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
- // TODO(b/240939253): update copies
- val title = getString(R.string.media_projection_dialog_service_title)
+ val title = getString(R.string.media_projection_permission_app_selector_title)
intent.putExtra(Intent.EXTRA_TITLE, title)
super.onCreate(bundle)
controller.init()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index e38c1ba..baeee9f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -610,7 +610,11 @@
// are
// elements in mediaPlayers.
if (MediaPlayerData.players().size != mediaContent.childCount) {
- Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+ throw IllegalStateException(
+ "Size of players list and number of views in carousel are out of sync. " +
+ "Players size is ${MediaPlayerData.players().size}. " +
+ "View count is ${mediaContent.childCount}."
+ )
}
return existingPlayer == null
}
@@ -667,7 +671,11 @@
// are
// elements in mediaPlayers.
if (MediaPlayerData.players().size != mediaContent.childCount) {
- Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync")
+ throw IllegalStateException(
+ "Size of players list and number of views in carousel are out of sync. " +
+ "Players size is ${MediaPlayerData.players().size}. " +
+ "View count is ${mediaContent.childCount}."
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
new file mode 100644
index 0000000..1324d2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.motiontool
+
+import android.view.WindowManagerGlobal
+import com.android.app.motiontool.DdmHandleMotionTool
+import com.android.app.motiontool.MotionToolManager
+import com.android.app.viewcapture.ViewCapture
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface MotionToolModule {
+
+ companion object {
+
+ @Provides
+ fun provideDdmHandleMotionTool(motionToolManager: MotionToolManager): DdmHandleMotionTool {
+ return DdmHandleMotionTool.getInstance(motionToolManager)
+ }
+
+ @Provides
+ fun provideMotionToolManager(
+ viewCapture: ViewCapture,
+ windowManagerGlobal: WindowManagerGlobal
+ ): MotionToolManager {
+ return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+ }
+
+ @Provides
+ fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
+
+ @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
+ }
+
+ @Binds
+ @IntoMap
+ @ClassKey(MotionToolStartable::class)
+ fun bindMotionToolStartable(impl: MotionToolStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt
new file mode 100644
index 0000000..fbb9538
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.motiontool
+
+import com.android.app.motiontool.DdmHandleMotionTool
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class MotionToolStartable
+@Inject
+internal constructor(private val ddmHandleMotionTool: DdmHandleMotionTool) : CoreStartable {
+
+ override fun start() {
+ ddmHandleMotionTool.register()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 50cf63d..b9f5859 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -87,14 +87,18 @@
import android.view.HapticFeedbackConstants;
import android.view.InsetsFrameProvider;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
@@ -366,15 +370,6 @@
}
@Override
- public void onQuickStepStarted() {
- // Use navbar dragging as a signal to hide the rotate button
- mView.getRotationButtonController().setRotateSuggestionButtonState(false);
-
- // Hide the notifications panel when quick step starts
- mShadeController.collapsePanel(true /* animate */);
- }
-
- @Override
public void onPrioritizedRotation(@Surface.Rotation int rotation) {
mStartingQuickSwitchRotation = rotation;
if (rotation == -1) {
@@ -487,6 +482,24 @@
}
};
+ private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
+ new SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceReplaced(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+ };
+
@Inject
NavigationBar(
NavigationBarView navigationBarView,
@@ -696,7 +709,8 @@
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+ mView.setComponents(
+ mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController());
}
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
@@ -717,6 +731,8 @@
mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
mOnComputeInternalInsetsListener);
+ mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
+ notifyNavigationBarSurface();
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -795,6 +811,10 @@
mHandler.removeCallbacks(mEnableLayoutTransitions);
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+ }
mFrame = null;
mOrientationHandle = null;
}
@@ -947,6 +967,12 @@
}
}
+ private void notifyNavigationBarSurface() {
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ SurfaceControl surface = viewRoot != null ? viewRoot.getSurfaceControl() : null;
+ mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
+ }
+
private int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
@@ -1059,7 +1085,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
@@ -1272,8 +1298,8 @@
}
private void onVerticalChanged(boolean isVertical) {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- statusBar -> statusBar.setQsScrimEnabled(!isVertical));
+ mCentralSurfacesOptionalLazy.get().ifPresent(statusBar ->
+ statusBar.getNotificationPanelViewController().setQsScrimEnabled(!isVertical));
}
private boolean onNavigationTouch(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 73fc21e..eb87ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -49,8 +49,8 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
-import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -355,7 +355,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
- InsetsVisibilities requestedVisibilities, String packageName,
+ @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
boolean nbModeChanged = false;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 622f5a2..83c2a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -412,10 +412,6 @@
logSomePresses(action, flags);
if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
- if (action == MotionEvent.ACTION_UP) {
- mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
- -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
- }
}
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 10ff48b..7964d16 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -289,8 +289,6 @@
mBackAnimation.setTriggerBack(true);
}
- mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
logGesture(mInRejectedExclusion
? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
: SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
@@ -302,8 +300,6 @@
mBackAnimation.setTriggerBack(false);
}
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
- mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
}
@Override
@@ -804,9 +800,6 @@
if (mExcludeRegion.contains(x, y)) {
if (withinRange) {
- // Log as exclusion only if it is in acceptable range in the first place.
- mOverviewProxyService.notifyBackAction(
- false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
// We don't have the end point for logging purposes.
mEndPoint.x = -1;
mEndPoint.y = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index d247f24..b964b76 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -22,7 +22,7 @@
import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.kotlin.getOrNull
-import com.android.wm.shell.floating.FloatingTasks
+import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import javax.inject.Inject
@@ -39,7 +39,7 @@
constructor(
private val context: Context,
private val intentResolver: NoteTaskIntentResolver,
- private val optionalFloatingTasks: Optional<FloatingTasks>,
+ private val optionalBubbles: Optional<Bubbles>,
private val optionalKeyguardManager: Optional<KeyguardManager>,
private val optionalUserManager: Optional<UserManager>,
@NoteTaskEnabledKey private val isEnabled: Boolean,
@@ -54,7 +54,7 @@
}
private fun showNoteTask() {
- val floatingTasks = optionalFloatingTasks.getOrNull() ?: return
+ val bubbles = optionalBubbles.getOrNull() ?: return
val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
val userManager = optionalUserManager.getOrNull() ?: return
val intent = intentResolver.resolveIntent() ?: return
@@ -66,7 +66,7 @@
context.startActivity(intent)
} else {
// TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
- floatingTasks.showOrSetStashed(intent)
+ bubbles.showAppBubble(intent)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index d84717d..0a5b600 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -17,7 +17,7 @@
package com.android.systemui.notetask
import com.android.systemui.statusbar.CommandQueue
-import com.android.wm.shell.floating.FloatingTasks
+import com.android.wm.shell.bubbles.Bubbles
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -26,7 +26,7 @@
internal class NoteTaskInitializer
@Inject
constructor(
- private val optionalFloatingTasks: Optional<FloatingTasks>,
+ private val optionalBubbles: Optional<Bubbles>,
private val lazyNoteTaskController: Lazy<NoteTaskController>,
private val commandQueue: CommandQueue,
@NoteTaskEnabledKey private val isEnabled: Boolean,
@@ -40,7 +40,7 @@
}
fun initialize() {
- if (isEnabled && optionalFloatingTasks.isPresent) {
+ if (isEnabled && optionalBubbles.isPresent) {
commandQueue.addCallback(callbacks)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e9a6c25..1f92b12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -140,7 +140,7 @@
iv.setTag(R.id.qs_icon_tag, icon);
iv.setTag(R.id.qs_slash_tag, state.slash);
iv.setPadding(0, padding, 0, padding);
- if (d instanceof Animatable2) {
+ if (shouldAnimate && d instanceof Animatable2) {
Animatable2 a = (Animatable2) d;
a.start();
if (state.isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66be00d..ba97297 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -64,6 +64,7 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
@@ -107,7 +108,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import java.util.function.BiConsumer;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -149,6 +149,7 @@
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
+ private SurfaceControl mNavigationBarSurface;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
@@ -190,7 +191,8 @@
// TODO move this logic to message queue
mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
if (event.getActionMasked() == ACTION_DOWN) {
- centralSurfaces.getPanelController().startExpandLatencyTracking();
+ centralSurfaces.getNotificationPanelViewController()
+ .startExpandLatencyTracking();
}
mHandler.post(() -> {
int action = event.getActionMasked();
@@ -217,17 +219,15 @@
}
@Override
- public void onBackPressed() throws RemoteException {
+ public void onBackPressed() {
verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-
- notifyBackAction(true, -1, -1, true, false);
});
}
@Override
- public void onImeSwitcherPressed() throws RemoteException {
+ public void onImeSwitcherPressed() {
// TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since
// Launcher/Taskbar isn't display aware.
mContext.getSystemService(InputMethodManager.class)
@@ -316,12 +316,6 @@
}
@Override
- public void notifySwipeUpGestureStarted() {
- verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
- notifySwipeUpGestureStartedInternal());
- }
-
- @Override
public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
notifyPrioritizedRotationInternal(rotation));
@@ -443,6 +437,7 @@
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
+ dispatchNavigationBarSurface();
// Force-update the systemui state flags
updateSystemUiStateFlags();
@@ -474,8 +469,6 @@
};
private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
- private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
- this::notifySplitScreenBoundsChanged;
// This is the death handler for the binder from the launcher service
private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
@@ -597,11 +590,18 @@
.commitUpdate(mContext.getDisplayId());
}
- public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) {
+ /**
+ * Called when the navigation bar surface is created or changed
+ */
+ public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
+ mNavigationBarSurface = navbarSurface;
+ dispatchNavigationBarSurface();
+ }
+
+ private void dispatchNavigationBarSurface() {
try {
if (mOverviewProxy != null) {
- mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
+ mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify back action", e);
@@ -614,7 +614,7 @@
final NavigationBarView navBarView =
mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
final NotificationPanelViewController panelController =
- mCentralSurfacesOptionalLazy.get().get().getPanelController();
+ mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController();
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
+ " navBarView=" + navBarView + " panelController=" + panelController);
@@ -800,24 +800,12 @@
}
}
- public void notifyQuickStepStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickStepStarted();
- }
- }
-
private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onPrioritizedRotation(rotation);
}
}
- public void notifyQuickScrubStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickScrubStarted();
- }
- }
-
private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onAssistantProgress(progress);
@@ -836,12 +824,6 @@
}
}
- private void notifySwipeUpGestureStartedInternal() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onSwipeUpGestureStarted();
- }
- }
-
public void notifyAssistantVisibilityChanged(float visibility) {
try {
if (mOverviewProxy != null) {
@@ -854,26 +836,6 @@
}
}
- /**
- * Notifies the Launcher of split screen size changes
- *
- * @param secondaryWindowBounds Bounds of the secondary window including the insets
- * @param secondaryWindowInsets stable insets received by the secondary window
- */
- public void notifySplitScreenBoundsChanged(
- Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
- try {
- if (mOverviewProxy != null) {
- mOverviewProxy.onSplitScreenSecondaryBoundsChanged(
- secondaryWindowBounds, secondaryWindowInsets);
- } else {
- Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds.");
- }
- } catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e);
- }
- }
-
private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() {
/**
* Notifies the Launcher that screen turned on and ready to use
@@ -1005,23 +967,20 @@
pw.print(" mWindowCornerRadius="); pw.println(mWindowCornerRadius);
pw.print(" mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
+ pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface);
pw.print(" mNavBarMode="); pw.println(mNavBarMode);
mSysUiState.dump(pw, args);
}
public interface OverviewProxyListener {
default void onConnectionChanged(boolean isConnected) {}
- default void onQuickStepStarted() {}
- default void onSwipeUpGestureStarted() {}
default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
default void onOverviewShown(boolean fromHome) {}
- default void onQuickScrubStarted() {}
/** Notify the recents app (overview) is started by 3-button navigation. */
default void onToggleRecentApps() {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
default void onTaskbarAutohideSuspend(boolean suspend) {}
- default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
default void startAssistant(Bundle bundle) {}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
new file mode 100644
index 0000000..731b177
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.sensorprivacy.television;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+
+import android.annotation.DimenRes;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.hardware.SensorPrivacyManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.tv.TvBottomSheetActivity;
+import com.android.systemui.util.settings.GlobalSettings;
+
+import javax.inject.Inject;
+
+/**
+ * Bottom sheet that is shown when the camera/mic sensors privacy state changed
+ * by the global software toggle or physical privacy switch.
+ */
+public class TvSensorPrivacyChangedActivity extends TvBottomSheetActivity {
+
+ private static final String TAG = TvSensorPrivacyChangedActivity.class.getSimpleName();
+
+ private static final int ALL_SENSORS = Integer.MAX_VALUE;
+
+ private int mSensor = -1;
+ private int mToggleType = -1;
+
+ private final GlobalSettings mGlobalSettings;
+ private final IndividualSensorPrivacyController mSensorPrivacyController;
+ private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
+ private TextView mTitle;
+ private TextView mContent;
+ private ImageView mIcon;
+ private ImageView mSecondIcon;
+ private Button mPositiveButton;
+ private Button mCancelButton;
+
+ @Inject
+ public TvSensorPrivacyChangedActivity(
+ IndividualSensorPrivacyController individualSensorPrivacyController,
+ GlobalSettings globalSettings) {
+ mSensorPrivacyController = individualSensorPrivacyController;
+ mGlobalSettings = globalSettings;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
+ false);
+ if (allSensors) {
+ mSensor = ALL_SENSORS;
+ } else {
+ mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1);
+ }
+
+ mToggleType = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_TOGGLE_TYPE, -1);
+
+ if (mSensor == -1 || mToggleType == -1) {
+ Log.v(TAG, "Invalid extras");
+ finish();
+ return;
+ }
+
+ // Do not show for software toggles
+ if (mToggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
+ finish();
+ return;
+ }
+
+ mSensorPrivacyCallback = (sensor, blocked) -> {
+ updateUI();
+ };
+
+ initUI();
+ }
+
+ private void initUI() {
+ mTitle = findViewById(R.id.bottom_sheet_title);
+ mContent = findViewById(R.id.bottom_sheet_body);
+ mIcon = findViewById(R.id.bottom_sheet_icon);
+ // mic icon if both icons are shown
+ mSecondIcon = findViewById(R.id.bottom_sheet_second_icon);
+ mPositiveButton = findViewById(R.id.bottom_sheet_positive_button);
+ mCancelButton = findViewById(R.id.bottom_sheet_negative_button);
+
+ mCancelButton.setText(android.R.string.cancel);
+ mCancelButton.setOnClickListener(v -> finish());
+
+ updateUI();
+ }
+
+ private void updateUI() {
+ final Resources resources = getResources();
+ setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
+ setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
+
+ switch (mSensor) {
+ case CAMERA:
+ updateUiForCameraUpdate(
+ mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA));
+ break;
+ case MICROPHONE:
+ default:
+ updateUiForMicUpdate(
+ mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE));
+ break;
+ }
+
+ // Start animation if drawable is animated
+ Drawable iconDrawable = mIcon.getDrawable();
+ if (iconDrawable instanceof Animatable) {
+ ((Animatable) iconDrawable).start();
+ }
+
+ mPositiveButton.setVisibility(View.GONE);
+ mCancelButton.setText(android.R.string.ok);
+ }
+
+ private void updateUiForMicUpdate(boolean blocked) {
+ if (blocked) {
+ mTitle.setText(R.string.sensor_privacy_mic_turned_off_dialog_title);
+ if (isExplicitUserInteractionAudioBypassAllowed()) {
+ mContent.setText(R.string.sensor_privacy_mic_blocked_with_exception_dialog_content);
+ } else {
+ mContent.setText(R.string.sensor_privacy_mic_blocked_no_exception_dialog_content);
+ }
+ mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
+ mSecondIcon.setVisibility(View.GONE);
+ } else {
+ mTitle.setText(R.string.sensor_privacy_mic_turned_on_dialog_title);
+ mContent.setText(R.string.sensor_privacy_mic_unblocked_dialog_content);
+ mIcon.setImageResource(com.android.internal.R.drawable.ic_mic_allowed);
+ mSecondIcon.setVisibility(View.GONE);
+ }
+ }
+
+ private void updateUiForCameraUpdate(boolean blocked) {
+ if (blocked) {
+ mTitle.setText(R.string.sensor_privacy_camera_turned_off_dialog_title);
+ mContent.setText(R.string.sensor_privacy_camera_blocked_dialog_content);
+ mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
+ mSecondIcon.setVisibility(View.GONE);
+ } else {
+ mTitle.setText(R.string.sensor_privacy_camera_turned_on_dialog_title);
+ mContent.setText(R.string.sensor_privacy_camera_unblocked_dialog_content);
+ mIcon.setImageResource(com.android.internal.R.drawable.ic_camera_allowed);
+ mSecondIcon.setVisibility(View.GONE);
+ }
+ }
+
+ private void setIconTint(boolean enableTint) {
+ final Resources resources = getResources();
+
+ if (enableTint) {
+ final ColorStateList iconTint = resources.getColorStateList(
+ R.color.bottom_sheet_icon_color, getTheme());
+ mIcon.setImageTintList(iconTint);
+ mSecondIcon.setImageTintList(iconTint);
+ } else {
+ mIcon.setImageTintList(null);
+ mSecondIcon.setImageTintList(null);
+ }
+
+ mIcon.invalidate();
+ mSecondIcon.invalidate();
+ }
+
+ private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) {
+ final Resources resources = getResources();
+ final int iconWidth = resources.getDimensionPixelSize(widthRes);
+ final int iconHeight = resources.getDimensionPixelSize(heightRes);
+
+ mIcon.getLayoutParams().width = iconWidth;
+ mIcon.getLayoutParams().height = iconHeight;
+ mIcon.invalidate();
+
+ mSecondIcon.getLayoutParams().width = iconWidth;
+ mSecondIcon.getLayoutParams().height = iconHeight;
+ mSecondIcon.invalidate();
+ }
+
+ private boolean isExplicitUserInteractionAudioBypassAllowed() {
+ return mGlobalSettings.getInt(
+ Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) == 1;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ updateUI();
+ mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
+ }
+
+ @Override
+ public void onPause() {
+ mSensorPrivacyController.removeCallback(mSensorPrivacyCallback);
+ super.onPause();
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index d543eb2..1b9657f 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -16,17 +16,23 @@
package com.android.systemui.sensorprivacy.television;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.hardware.SensorPrivacyManager.Sources.OTHER;
import android.annotation.DimenRes;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.hardware.SensorPrivacyManager;
import android.os.Bundle;
+import android.os.UserHandle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@@ -39,6 +45,8 @@
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.tv.TvBottomSheetActivity;
+import java.util.List;
+
import javax.inject.Inject;
/**
@@ -57,6 +65,8 @@
private int mSensor = -1;
+ private final AppOpsManager mAppOpsManager;
+ private final RoleManager mRoleManager;
private final IndividualSensorPrivacyController mSensorPrivacyController;
private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
private TextView mTitle;
@@ -68,8 +78,11 @@
@Inject
public TvUnblockSensorActivity(
- IndividualSensorPrivacyController individualSensorPrivacyController) {
+ IndividualSensorPrivacyController individualSensorPrivacyController,
+ AppOpsManager appOpsManager, RoleManager roleManager) {
mSensorPrivacyController = individualSensorPrivacyController;
+ mAppOpsManager = appOpsManager;
+ mRoleManager = roleManager;
}
@Override
@@ -120,6 +133,10 @@
toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content;
break;
}
+ showToastAndFinish(toastMsgResId);
+ }
+
+ private void showToastAndFinish(int toastMsgResId) {
Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show();
finish();
}
@@ -149,7 +166,9 @@
}
private void updateUI() {
- if (isBlockedByHardwareToggle()) {
+ if (isHTTAccessDisabled()) {
+ updateUiForHTT();
+ } else if (isBlockedByHardwareToggle()) {
updateUiForHardwareToggle();
} else {
updateUiForSoftwareToggle();
@@ -208,20 +227,20 @@
switch (mSensor) {
case MICROPHONE:
- mTitle.setText(R.string.sensor_privacy_start_use_mic_dialog_title);
+ mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_mic_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
mSecondIcon.setVisibility(View.GONE);
break;
case CAMERA:
- mTitle.setText(R.string.sensor_privacy_start_use_camera_dialog_title);
+ mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_camera_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
mSecondIcon.setVisibility(View.GONE);
break;
case ALL_SENSORS:
default:
- mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_title);
+ mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
mSecondIcon.setImageResource(
@@ -241,6 +260,29 @@
});
}
+ private void updateUiForHTT() {
+ setIconTint(true);
+ setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
+
+ mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
+ mContent.setText(R.string.sensor_privacy_htt_blocked_dialog_content);
+ mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
+ mSecondIcon.setVisibility(View.GONE);
+
+ mPositiveButton.setText(R.string.sensor_privacy_dialog_open_settings);
+ mPositiveButton.setOnClickListener(v -> {
+ Intent openPrivacySettings = new Intent(ACTION_MANAGE_MICROPHONE_PRIVACY);
+ ActivityInfo activityInfo = openPrivacySettings.resolveActivityInfo(getPackageManager(),
+ MATCH_SYSTEM_ONLY);
+ if (activityInfo == null) {
+ showToastAndFinish(com.android.internal.R.string.noApplications);
+ } else {
+ startActivity(openPrivacySettings);
+ finish();
+ }
+ });
+ }
+
private void setIconTint(boolean enableTint) {
final Resources resources = getResources();
@@ -272,6 +314,18 @@
mSecondIcon.invalidate();
}
+ private boolean isHTTAccessDisabled() {
+ String pkg = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ List<String> assistantPkgs = mRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT);
+ if (!assistantPkgs.contains(pkg)) {
+ return false;
+ }
+
+ return (mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, UserHandle.myUserId(),
+ pkg) != AppOpsManager.MODE_ALLOWED);
+ }
+
@Override
public void onResume() {
super.onResume();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 33da64d..59b1ed7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -455,7 +455,6 @@
* need to take this into account in our panel height calculation.
*/
private boolean mQsAnimatorExpand;
- private boolean mIsLaunchTransitionFinished;
private ValueAnimator mQsSizeChangeAnimator;
private boolean mQsScrimEnabled = true;
private boolean mQsTouchAboveFalsingThreshold;
@@ -531,7 +530,7 @@
private final NavigationBarController mNavigationBarController;
private final int mDisplayId;
- private KeyguardIndicationController mKeyguardIndicationController;
+ private final KeyguardIndicationController mKeyguardIndicationController;
private int mHeadsUpInset;
private boolean mHeadsUpPinnedMode;
private boolean mAllowExpandForSmallExpansion;
@@ -743,6 +742,7 @@
SysUiState sysUiState,
Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@@ -779,6 +779,7 @@
mResources = mView.getResources();
mKeyguardStateController = keyguardStateController;
+ mKeyguardIndicationController = keyguardIndicationController;
mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
mNotificationShadeWindowController = notificationShadeWindowController;
FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get();
@@ -1020,7 +1021,7 @@
mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
mOnEmptySpaceClickListener);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
- mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+ setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
initBottomArea();
@@ -1264,7 +1265,7 @@
int index = mView.indexOfChild(mKeyguardBottomArea);
mView.removeView(mKeyguardBottomArea);
KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
- mKeyguardBottomArea = mKeyguardBottomAreaViewControllerProvider.get().getView();
+ setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
mKeyguardBottomArea.initFrom(oldBottomArea);
mView.addView(mKeyguardBottomArea, index);
initBottomArea();
@@ -1343,8 +1344,8 @@
return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
}
- public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
- mKeyguardIndicationController = indicationController;
+ private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
+ mKeyguardBottomArea = keyguardBottomArea;
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
}
@@ -1751,7 +1752,6 @@
}
public void resetViews(boolean animate) {
- mIsLaunchTransitionFinished = false;
mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
@@ -3777,10 +3777,6 @@
mQs.closeCustomizer();
}
- public boolean isLaunchTransitionFinished() {
- return mIsLaunchTransitionFinished;
- }
-
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
mIsLaunchAnimationRunning = running;
@@ -3880,6 +3876,7 @@
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
+ mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
mNotificationStackScrollLayoutController.getHeadsUpCallback(),
NotificationPanelViewController.this);
@@ -4361,10 +4358,6 @@
mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
- public ShadeHeadsUpChangedListener getOnHeadsUpChangedListener() {
- return mOnHeadsUpChangedListener;
- }
-
public void setHeaderDebugInfo(String text) {
if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
}
@@ -5682,6 +5675,7 @@
/** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
public boolean onInterceptTouchEvent(MotionEvent event) {
+ mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
if (SPEW_LOGCAT) {
Log.v(TAG,
"NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
@@ -5694,6 +5688,8 @@
// Do not let touches go to shade or QS if the bouncer is visible,
// but still let user swipe down to expand the panel, dismissing the bouncer.
if (mCentralSurfaces.isBouncerShowing()) {
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "bouncer is showing");
return true;
}
if (mCommandQueue.panelsEnabled()
@@ -5701,15 +5697,21 @@
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "HeadsUpTouchHelper");
return true;
}
if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
&& mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "PulseExpansionHandler");
return true;
}
if (!isFullyCollapsed() && onQsIntercept(event)) {
debugLog("onQsIntercept true");
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+ + "QsIntercept");
return true;
}
if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
@@ -5740,6 +5742,9 @@
if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
cancelHeightAnimator();
mTouchSlopExceeded = true;
+ mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:"
+ + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:"
+ + " false");
return true;
}
mInitialExpandY = y;
@@ -5784,6 +5789,8 @@
&& hAbs > Math.abs(x - mInitialExpandX)) {
cancelHeightAnimator();
startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+ mShadeLog.v("NotificationPanelViewController MotionEvent"
+ + " intercepted: startExpandMotion");
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 73c6d50..85b259e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.shade
import android.view.View
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index 084b7dc..bf622c9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -52,6 +52,7 @@
private val centralSurfaces: CentralSurfaces,
private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val statusBarStateController: StatusBarStateController,
+ private val shadeLogger: ShadeLogger,
tunerService: TunerService,
dumpManager: DumpManager
) : GestureDetector.SimpleOnGestureListener(), Dumpable {
@@ -77,18 +78,23 @@
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
- if (statusBarStateController.isDozing &&
- singleTapEnabled &&
- !dockManager.isDocked &&
- !falsingManager.isProximityNear &&
- !falsingManager.isFalseTap(LOW_PENALTY)
- ) {
- centralSurfaces.wakeUpIfDozing(
+ val isNotDocked = !dockManager.isDocked
+ shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked)
+ if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) {
+ val proximityIsNotNear = !falsingManager.isProximityNear
+ val isNotAFalseTap = !falsingManager.isFalseTap(LOW_PENALTY)
+ shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
+ if (proximityIsNotNear && isNotAFalseTap) {
+ shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
+ centralSurfaces.wakeUpIfDozing(
SystemClock.uptimeMillis(),
notificationShadeWindowView,
- "PULSING_SINGLE_TAP")
+ "PULSING_SINGLE_TAP"
+ )
+ }
return true
}
+ shadeLogger.d("onSingleTapUp event ignored")
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index f389dd9..eaf7fae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -224,6 +224,6 @@
}
private NotificationPanelViewController getNotificationPanelViewController() {
- return getCentralSurfaces().getPanelController();
+ return getCentralSurfaces().getNotificationPanelViewController();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 7f1bba3..40ed40a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.shade
import android.view.MotionEvent
@@ -16,6 +32,10 @@
buffer.log(TAG, LogLevel.VERBOSE, msg)
}
+ fun d(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, LogLevel.DEBUG, msg)
+ }
+
private inline fun log(
logLevel: LogLevel,
initializer: LogMessage.() -> Unit,
@@ -123,4 +143,25 @@
"animatingQs=$long1"
})
}
+
+ fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) {
+ log(LogLevel.DEBUG, {
+ bool1 = isDozing
+ bool2 = singleTapEnabled
+ bool3 = isNotDocked
+ }, {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3"
+ })
+ }
+
+ fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) {
+ log(LogLevel.DEBUG, {
+ bool1 = proximityIsNotNear
+ bool2 = isNotFalseTap
+ }, {
+ "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+ "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2"
+ })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
index f4db3ab..8847dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.shade.transition
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index a77c21a..218e897 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.shade.transition
import android.content.res.Configuration
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
index 22e847d..a4642e0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.shade.transition
import com.android.systemui.shade.PanelState
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 1e8208f..1054aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.shade.transition
import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
index 8c57194..fde08ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.systemui.shade.transition
import android.animation.Animator
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 4ae0f6a..f786ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -39,7 +39,7 @@
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
@@ -53,7 +53,7 @@
import android.util.Pair;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -154,7 +154,7 @@
//TODO(b/169175022) Update name and when feature name is locked.
private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 58 << MSG_SHIFT;
private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT;
- private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
+ private static final int MSG_SET_UDFPS_REFRESH_RATE_CALLBACK = 60 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT;
private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT;
@@ -333,9 +333,9 @@
}
/**
- * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+ * @see IStatusBar#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)
*/
- default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ default void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
}
/**
@@ -356,11 +356,11 @@
default void onRecentsAnimationStateChanged(boolean running) { }
/**
- * @see IStatusBar#onSystemBarAttributesChanged.
+ * @see IStatusBar#onSystemBarAttributesChanged
*/
default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes,
String packageName, LetterboxDetails[] letterboxDetails) { }
/**
@@ -1017,9 +1017,9 @@
}
@Override
- public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
+ mHandler.obtainMessage(MSG_SET_UDFPS_REFRESH_RATE_CALLBACK, callback).sendToTarget();
}
}
@@ -1090,7 +1090,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
@@ -1099,7 +1099,7 @@
args.argi3 = navbarColorManagedByIme ? 1 : 0;
args.arg1 = appearanceRegions;
args.argi4 = behavior;
- args.arg2 = requestedVisibilities;
+ args.argi5 = requestedVisibleTypes;
args.arg3 = packageName;
args.arg4 = letterboxDetails;
mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
@@ -1546,9 +1546,10 @@
(IBiometricContextListener) msg.obj);
}
break;
- case MSG_SET_UDFPS_HBM_LISTENER:
+ case MSG_SET_UDFPS_REFRESH_RATE_CALLBACK:
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
+ mCallbacks.get(i).setUdfpsRefreshRateCallback(
+ (IUdfpsRefreshRateRequestCallback) msg.obj);
}
break;
case MSG_SHOW_CHARGING_ANIMATION:
@@ -1581,8 +1582,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
(AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
- (InsetsVisibilities) args.arg2, (String) args.arg3,
- (LetterboxDetails[]) args.arg4);
+ args.argi5, (String) args.arg3, (LetterboxDetails[]) args.arg4);
}
args.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index c04bc82..fdec745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
@@ -34,9 +32,10 @@
import android.util.Log;
import android.view.Choreographer;
import android.view.InsetsFlags;
-import android.view.InsetsVisibilities;
import android.view.View;
import android.view.ViewDebug;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import android.view.animation.Interpolator;
@@ -497,9 +496,9 @@
@Override
public void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
- InsetsVisibilities requestedVisibilities, String packageName) {
- boolean isFullscreen = !requestedVisibilities.getVisibility(ITYPE_STATUS_BAR)
- || !requestedVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
+ @InsetsType int requestedVisibleTypes, String packageName) {
+ boolean isFullscreen = (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0
+ || (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0;
if (mIsFullscreen != isFullscreen) {
mIsFullscreen = isFullscreen;
synchronized (mListeners) {
@@ -514,12 +513,12 @@
if (DEBUG_IMMERSIVE_APPS) {
boolean dim = (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0;
String behaviorName = ViewDebug.flagsToString(InsetsFlags.class, "behavior", behavior);
- String requestedVisibilityString = requestedVisibilities.toString();
- if (requestedVisibilityString.isEmpty()) {
- requestedVisibilityString = "none";
+ String requestedVisibleTypesString = WindowInsets.Type.toString(requestedVisibleTypes);
+ if (requestedVisibleTypesString.isEmpty()) {
+ requestedVisibleTypesString = "none";
}
Log.d(TAG, packageName + " dim=" + dim + " behavior=" + behaviorName
- + " requested visibilities: " + requestedVisibilityString);
+ + " requested visible types: " + requestedVisibleTypesString);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 2cc7738..1189107 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,8 +19,8 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
-import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -154,7 +154,7 @@
* Set the system bar attributes
*/
void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
- InsetsVisibilities requestedVisibilities, String packageName);
+ @InsetsType int requestedVisibleTypes, String packageName);
/**
* Set pulsing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 470cbcb..5dbb4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
@@ -68,6 +69,7 @@
private val mHeadsUpViewBinder: HeadsUpViewBinder,
private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
private val mRemoteInputManager: NotificationRemoteInputManager,
+ private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
@IncomingHeader private val mIncomingHeaderController: NodeController,
@Main private val mExecutor: DelayableExecutor,
) : Coordinator {
@@ -380,6 +382,12 @@
* Notification was just added and if it should heads up, bind the view and then show it.
*/
override fun onEntryAdded(entry: NotificationEntry) {
+ // First check whether this notification should launch a full screen intent, and
+ // launch it if needed.
+ if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
+ mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+ }
+
// shouldHeadsUp includes check for whether this notification should be filtered
val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
mPostedEntries[entry.key] = PostedEntry(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt
new file mode 100644
index 0000000..74ff78e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/**
+ * A class that enables communication of decisions to launch a notification's full screen intent.
+ */
+@SysUISingleton
+class LaunchFullScreenIntentProvider @Inject constructor() {
+ companion object {
+ private const val TAG = "LaunchFullScreenIntentProvider"
+ }
+ private val listeners = ListenerSet<Listener>()
+
+ /**
+ * Registers a listener with this provider. These listeners will be alerted whenever a full
+ * screen intent should be launched for a notification entry.
+ */
+ fun registerListener(listener: Listener) {
+ listeners.addIfAbsent(listener)
+ }
+
+ /** Removes the specified listener. */
+ fun removeListener(listener: Listener) {
+ listeners.remove(listener)
+ }
+
+ /**
+ * Sends a request to launch full screen intent for the given notification entry to all
+ * registered listeners.
+ */
+ fun launchFullScreenIntent(entry: NotificationEntry) {
+ if (listeners.isEmpty()) {
+ // This should never happen, but we should definitely know if it does because having
+ // no listeners would indicate that FSIs are getting entirely dropped on the floor.
+ Log.wtf(TAG, "no listeners found when launchFullScreenIntent requested")
+ }
+ for (listener in listeners) {
+ listener.onFullScreenIntentRequested(entry)
+ }
+ }
+
+ /** Listener interface for passing full screen intent launch decisions. */
+ fun interface Listener {
+ /**
+ * Invoked whenever a full screen intent launch is requested for the given notification
+ * entry.
+ */
+ fun onFullScreenIntentRequested(entry: NotificationEntry)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9e7717c..de158c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1341,21 +1341,6 @@
return mOnKeyguard;
}
- public void removeAllChildren() {
- List<ExpandableNotificationRow> notificationChildren =
- mChildrenContainer.getAttachedChildren();
- ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren);
- for (int i = 0; i < clonedList.size(); i++) {
- ExpandableNotificationRow row = clonedList.get(i);
- if (row.keepInParent()) {
- continue;
- }
- mChildrenContainer.removeNotification(row);
- row.setIsChildInGroup(false, null);
- }
- onAttachedChildrenCountChanged();
- }
-
@Override
public void dismiss(boolean refocusOnDismiss) {
super.dismiss(refocusOnDismiss);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index c4ef28e..2c3330e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -114,6 +114,8 @@
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.LargeScreenUtils;
+import com.google.errorprone.annotations.CompileTimeConstant;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.util.ArrayList;
@@ -3693,6 +3695,8 @@
@ShadeViewRefactor(RefactorComponent.INPUT)
void handleEmptySpaceClick(MotionEvent ev) {
+ logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY),
+ mStatusBarState, mTouchIsClick);
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
final float touchSlop = getTouchSlop(ev);
@@ -3704,12 +3708,34 @@
case MotionEvent.ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
+ debugLog("handleEmptySpaceClick: touch event propagated further");
mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
}
break;
+ default:
+ debugLog("handleEmptySpaceClick: MotionEvent ignored");
}
}
+ private void debugLog(@CompileTimeConstant String s) {
+ if (mLogger == null) {
+ return;
+ }
+ mLogger.d(s);
+ }
+
+ private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
+ int statusBarState, boolean touchIsClick) {
+ if (mLogger == null) {
+ return;
+ }
+ mLogger.logEmptySpaceClick(
+ isTouchBelowLastNotification,
+ statusBarState,
+ touchIsClick,
+ MotionEvent.actionToString(ev.getActionMasked()));
+ }
+
@ShadeViewRefactor(RefactorComponent.INPUT)
void initDownStates(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 4c52db7..64dd6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -2,6 +2,7 @@
import com.android.systemui.log.dagger.NotificationHeadsUpLog
import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
@@ -10,6 +11,7 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
+import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
class NotificationStackScrollLogger @Inject constructor(
@@ -56,6 +58,25 @@
"key: $str1 expected: $bool1 actual: $bool2"
})
}
+
+ fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+
+ fun logEmptySpaceClick(
+ isBelowLastNotification: Boolean,
+ statusBarState: Int,
+ touchIsClick: Boolean,
+ motionEventDesc: String
+ ) {
+ buffer.log(TAG, DEBUG, {
+ int1 = statusBarState
+ bool1 = touchIsClick
+ bool2 = isBelowLastNotification
+ str1 = motionEventDesc
+ }, {
+ "handleEmptySpaceClick: statusBarState: $int1 isTouchAClick: $bool1 " +
+ "isTouchBelowNotification: $bool2 motionEvent: $str1"
+ })
+ }
}
private const val TAG = "NotificationStackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 9900e41..2f7d344 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -64,8 +64,10 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -163,7 +165,7 @@
private PendingAuthenticated mPendingAuthenticated = null;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
- private BiometricModeListener mBiometricModeListener;
+ private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>();
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
@@ -305,9 +307,14 @@
mKeyguardViewController = keyguardViewController;
}
- /** Sets a {@link BiometricModeListener}. */
- public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
- mBiometricModeListener = biometricModeListener;
+ /** Adds a {@link BiometricModeListener}. */
+ public void addBiometricModeListener(BiometricModeListener listener) {
+ mBiometricModeListeners.add(listener);
+ }
+
+ /** Removes a {@link BiometricModeListener}. */
+ public void removeBiometricModeListener(BiometricModeListener listener) {
+ mBiometricModeListeners.remove(listener);
}
private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -481,15 +488,12 @@
break;
}
onModeChanged(mMode);
- if (mBiometricModeListener != null) {
- mBiometricModeListener.notifyBiometricAuthModeChanged();
- }
Trace.endSection();
}
private void onModeChanged(@WakeAndUnlockMode int mode) {
- if (mBiometricModeListener != null) {
- mBiometricModeListener.onModeChanged(mode);
+ for (BiometricModeListener listener : mBiometricModeListeners) {
+ listener.onModeChanged(mode);
}
}
@@ -696,9 +700,8 @@
mMode = MODE_NONE;
mBiometricType = null;
mNotificationShadeWindowController.setForceDozeBrightness(false);
- if (mBiometricModeListener != null) {
- mBiometricModeListener.onResetMode();
- mBiometricModeListener.notifyBiometricAuthModeChanged();
+ for (BiometricModeListener listener : mBiometricModeListeners) {
+ listener.onResetMode();
}
mNumConsecutiveFpFailures = 0;
mLastFpFailureUptimeMillis = 0;
@@ -807,10 +810,8 @@
/** An interface to interact with the {@link BiometricUnlockController}. */
public interface BiometricModeListener {
/** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
- void onResetMode();
+ default void onResetMode() {}
/** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
- void onModeChanged(@WakeAndUnlockMode int mode);
- /** Called after processing {@link #onModeChanged(int)}. */
- void notifyBiometricAuthModeChanged();
+ default void onModeChanged(@WakeAndUnlockMode int mode) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 2504fc1..1961e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -196,8 +196,6 @@
void collapsePanelOnMainThread();
- void collapsePanelWithDuration(int duration);
-
void togglePanel();
void start();
@@ -305,9 +303,6 @@
void checkBarModes();
- // Called by NavigationBarFragment
- void setQsScrimEnabled(boolean scrimEnabled);
-
void updateBubblesVisibility();
void setInteracting(int barWindow, boolean interacting);
@@ -379,8 +374,6 @@
void showKeyguardImpl();
- boolean isInLaunchTransition();
-
void fadeKeyguardAfterLaunchTransition(Runnable beforeFading,
Runnable endRunnable, Runnable cancelRunnable);
@@ -437,8 +430,6 @@
void showPinningEscapeToast();
- KeyguardBottomAreaView getKeyguardBottomAreaView();
-
void setBouncerShowing(boolean bouncerShowing);
void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
@@ -505,12 +496,8 @@
boolean isBouncerShowingOverDream();
- void onBouncerPreHideAnimation();
-
boolean isKeyguardSecure();
- NotificationPanelViewController getPanelController();
-
NotificationGutsManager getGutsManager();
void updateNotificationPanelTouchState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index d6fadca..41f0520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -38,8 +38,8 @@
import android.util.Log;
import android.util.Slog;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.KeyEvent;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -458,7 +458,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
@@ -471,7 +471,7 @@
appearanceRegions,
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index eb7a742..6f952f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -466,7 +466,7 @@
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
private final PluginManager mPluginManager;
- private final com.android.systemui.shade.ShadeController mShadeController;
+ private final ShadeController mShadeController;
private final InitController mInitController;
private final PluginDependencyProvider mPluginDependencyProvider;
@@ -479,9 +479,9 @@
private final StatusBarSignalPolicy mStatusBarSignalPolicy;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
- // expanded notifications
- // the sliding/resizing panel within the notification window
- protected NotificationPanelViewController mNotificationPanelViewController;
+ /** Controller for the Shade. */
+ @VisibleForTesting
+ NotificationPanelViewController mNotificationPanelViewController;
// settings
private QSPanelController mQSPanelController;
@@ -928,7 +928,7 @@
}
mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
- result.mRequestedVisibilities, result.mPackageName, result.mLetterboxDetails);
+ result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
// StatusBarManagerService has a back up of IME token and it's restored here.
mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken,
@@ -1152,7 +1152,6 @@
initializer.initializeStatusBar(mCentralSurfacesComponent);
mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
- mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
createNavigationBar(result);
@@ -1161,9 +1160,6 @@
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
}
- mNotificationPanelViewController.setKeyguardIndicationController(
- mKeyguardIndicationController);
-
mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
R.id.ambient_indication_container);
@@ -1514,11 +1510,12 @@
protected void startKeyguard() {
Trace.beginSection("CentralSurfaces#startKeyguard");
mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
- mBiometricUnlockController.setBiometricModeListener(
+ mBiometricUnlockController.addBiometricModeListener(
new BiometricUnlockController.BiometricModeListener() {
@Override
public void onResetMode() {
setWakeAndUnlocking(false);
+ notifyBiometricAuthModeChanged();
}
@Override
@@ -1529,11 +1526,7 @@
case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
setWakeAndUnlocking(true);
}
- }
-
- @Override
- public void notifyBiometricAuthModeChanged() {
- CentralSurfacesImpl.this.notifyBiometricAuthModeChanged();
+ notifyBiometricAuthModeChanged();
}
private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
@@ -2001,8 +1994,7 @@
}
void makeExpandedInvisible() {
- if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
- + " mExpandedVisible=" + mExpandedVisible);
+ if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
if (!mExpandedVisible || mNotificationShadeWindowView == null) {
return;
@@ -2178,12 +2170,6 @@
mNoAnimationOnNextBarModeChange = false;
}
- // Called by NavigationBarFragment
- @Override
- public void setQsScrimEnabled(boolean scrimEnabled) {
- mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
- }
-
/** Temporarily hides Bubbles if the status bar is hidden. */
@Override
public void updateBubblesVisibility() {
@@ -2559,19 +2545,10 @@
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */, true /* delayed*/);
} else {
-
// Do it after DismissAction has been processed to conserve the needed
// ordering.
mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
}
- } else if (CentralSurfacesImpl.this.isInLaunchTransition()
- && mNotificationPanelViewController.isLaunchTransitionFinished()) {
-
- // We are not dismissing the shade, but the launch transition is already
- // finished,
- // so nobody will call readyForKeyguardDone anymore. Post it such that
- // keyguardDonePending gets called first.
- mMainExecutor.execute(mStatusBarKeyguardViewManager::readyForKeyguardDone);
}
return deferred;
}
@@ -2988,11 +2965,6 @@
mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
}
- @Override
- public boolean isInLaunchTransition() {
- return mNotificationPanelViewController.isLaunchTransitionFinished();
- }
-
/**
* Fades the content of the keyguard away after the launch transition is done.
*
@@ -3373,12 +3345,6 @@
}
}
- /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */
- @Override
- public void collapsePanelWithDuration(int duration) {
- mNotificationPanelViewController.collapseWithDuration(duration);
- }
-
/**
* Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
* from the power button).
@@ -3468,12 +3434,6 @@
mNavigationBarController.showPinningEscapeToast(mDisplayId);
}
- //TODO(b/254875405): this should be removed.
- @Override
- public KeyguardBottomAreaView getKeyguardBottomAreaView() {
- return mNotificationPanelViewController.getKeyguardBottomAreaView();
- }
-
protected ViewRootImpl getViewRootImpl() {
NotificationShadeWindowView nswv = getNotificationShadeWindowView();
if (nswv != null) return nswv.getViewRootImpl();
@@ -4176,23 +4136,11 @@
return mBouncerShowingOverDream;
}
- /**
- * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
- */
- @Override
- public void onBouncerPreHideAnimation() {
- mNotificationPanelViewController.startBouncerPreHideAnimation();
-
- }
-
@Override
public boolean isKeyguardSecure() {
return mStatusBarKeyguardViewManager.isSecure();
}
- @Override
- public NotificationPanelViewController getPanelController() {
- return mNotificationPanelViewController;
- }
+
// End Extra BaseStatusBarMethods.
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 6e98c49..eba7fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -22,8 +22,8 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import android.view.WindowManager;
@@ -144,7 +144,7 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes,
String packageName, LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 86f6ff8..0a0ded2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
import android.annotation.Nullable;
import android.content.Context;
@@ -53,8 +54,9 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
import com.android.systemui.util.Assert;
import java.util.ArrayList;
@@ -84,7 +86,18 @@
/** */
void setIcon(String slot, StatusBarIcon icon);
/** */
- void setSignalIcon(String slot, WifiIconState state);
+ void setWifiIcon(String slot, WifiIconState state);
+
+ /**
+ * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
+ * set up (inflated and added to the view hierarchy).
+ *
+ * This method completely replaces {@link #setWifiIcon} with the information from the new wifi
+ * data pipeline. Icons will automatically keep their state up to date, so we don't have to
+ * worry about funneling state objects through anymore.
+ */
+ void setNewWifiIcon();
+
/** */
void setMobileIcons(String slot, List<MobileIconState> states);
@@ -151,14 +164,14 @@
LinearLayout linearLayout,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
@@ -218,7 +231,7 @@
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
+ private final WifiUiAdapter mWifiUiAdapter;
private final MobileContextProvider mMobileContextProvider;
private final MobileUiAdapter mMobileUiAdapter;
private final DarkIconDispatcher mDarkIconDispatcher;
@@ -226,12 +239,12 @@
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileContextProvider mobileContextProvider,
MobileUiAdapter mobileUiAdapter,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
+ mWifiUiAdapter = wifiUiAdapter;
mMobileContextProvider = mobileContextProvider;
mMobileUiAdapter = mobileUiAdapter;
mDarkIconDispatcher = darkIconDispatcher;
@@ -242,7 +255,7 @@
group,
location,
mStatusBarPipelineFlags,
- mWifiViewModel,
+ mWifiUiAdapter,
mMobileUiAdapter,
mMobileContextProvider,
mDarkIconDispatcher);
@@ -260,14 +273,14 @@
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
super(group,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
mobileContextProvider);
}
@@ -302,19 +315,19 @@
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
+ private final WifiUiAdapter mWifiUiAdapter;
private final MobileContextProvider mMobileContextProvider;
private final MobileUiAdapter mMobileUiAdapter;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
+ mWifiUiAdapter = wifiUiAdapter;
mMobileUiAdapter = mobileUiAdapter;
mMobileContextProvider = mobileContextProvider;
}
@@ -324,7 +337,7 @@
group,
location,
mStatusBarPipelineFlags,
- mWifiViewModel,
+ mWifiUiAdapter,
mMobileUiAdapter,
mMobileContextProvider);
}
@@ -336,10 +349,9 @@
*/
class IconManager implements DemoModeCommandReceiver {
protected final ViewGroup mGroup;
- private final StatusBarLocation mLocation;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
+ private final LocationBasedWifiViewModel mWifiViewModel;
private final MobileIconsViewModel mMobileIconsViewModel;
protected final Context mContext;
@@ -359,26 +371,33 @@
ViewGroup group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider mobileContextProvider
) {
mGroup = group;
- mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_icon_size);
- if (statusBarPipelineFlags.useNewMobileIcons()) {
- // This starts the flow for the new pipeline, and will notify us of changes
+ if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
+ // This starts the flow for the new pipeline, and will notify us of changes if
+ // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
} else {
mMobileIconsViewModel = null;
}
+
+ if (statusBarPipelineFlags.runNewWifiIconBackend()) {
+ // This starts the flow for the new pipeline, and will notify us of changes if
+ // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
+ mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location);
+ } else {
+ mWifiViewModel = null;
+ }
}
public boolean isDemoable() {
@@ -429,6 +448,9 @@
case TYPE_WIFI:
return addWifiIcon(index, slot, holder.getWifiState());
+ case TYPE_WIFI_NEW:
+ return addNewWifiIcon(index, slot);
+
case TYPE_MOBILE:
return addMobileIcon(index, slot, holder.getMobileState());
@@ -450,16 +472,13 @@
@VisibleForTesting
protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
- final BaseStatusBarFrameLayout view;
if (mStatusBarPipelineFlags.useNewWifiIcon()) {
- view = onCreateModernStatusBarWifiView(slot);
- // When [ModernStatusBarWifiView] is created, it will automatically apply the
- // correct view state so we don't need to call applyWifiState.
- } else {
- StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot);
- wifiView.applyWifiState(state);
- view = wifiView;
+ throw new IllegalStateException("Attempting to add a mobile icon while the new "
+ + "icons are enabled is not supported");
}
+
+ final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
+ view.applyWifiState(state);
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
@@ -468,6 +487,17 @@
return view;
}
+ protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
+ if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+ throw new IllegalStateException("Attempting to add a wifi icon using the new"
+ + "pipeline, but the enabled flag is false.");
+ }
+
+ ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
+ mGroup.addView(view, index, onCreateLayoutParams());
+ return view;
+ }
+
@VisibleForTesting
protected StatusIconDisplayable addMobileIcon(
int index,
@@ -523,8 +553,7 @@
}
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
- return ModernStatusBarWifiView.constructAndBind(
- mContext, slot, mWifiViewModel, mLocation);
+ return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
}
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
@@ -600,7 +629,8 @@
onSetMobileIcon(viewIndex, holder.getMobileState());
return;
case TYPE_MOBILE_NEW:
- // Nothing, the icon updates itself now
+ case TYPE_WIFI_NEW:
+ // Nothing, the new icons update themselves
return;
default:
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 31e960a..674e574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -195,12 +195,13 @@
}
}
- /**
- * Signal icons need to be handled differently, because they can be
- * composite views
- */
@Override
- public void setSignalIcon(String slot, WifiIconState state) {
+ public void setWifiIcon(String slot, WifiIconState state) {
+ if (mStatusBarPipelineFlags.useNewWifiIcon()) {
+ Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled");
+ return;
+ }
+
if (state == null) {
removeIcon(slot, 0);
return;
@@ -216,6 +217,24 @@
}
}
+
+ @Override
+ public void setNewWifiIcon() {
+ if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+ Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled");
+ return;
+ }
+
+ String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi);
+ StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0);
+ if (holder == null) {
+ holder = StatusBarIconHolder.forNewWifiIcon();
+ setIcon(slot, holder);
+ } else {
+ // Don't have to do anything in the new world
+ }
+ }
+
/**
* Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
* by subId. Don't worry this definitely makes sense and works.
@@ -225,7 +244,7 @@
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
if (mStatusBarPipelineFlags.useNewMobileIcons()) {
- Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+ Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile "
+ "icons are enabled");
return;
}
@@ -251,10 +270,11 @@
public void setNewMobileIconSubIds(List<Integer> subIds) {
if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
Log.d(TAG, "ignoring new pipeline callback, "
- + "since the new icons are disabled");
+ + "since the new mobile icons are disabled");
return;
}
- Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+ String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
+ Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
Collections.reverse(subIds);
@@ -262,7 +282,7 @@
StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
if (holder == null) {
holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
- setIcon("mobile", holder);
+ setIcon(slotName, holder);
} else {
// Don't have to do anything in the new world
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 68a203e..f6c0da8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -51,11 +51,24 @@
@Deprecated
public static final int TYPE_MOBILE_NEW = 3;
+ /**
+ * TODO (b/238425913): address this once the new pipeline is in place
+ * This type exists so that the new wifi pipeline can be used to inform the old view system
+ * about the existence of the wifi icon. The design of the new pipeline should allow for removal
+ * of this icon holder type, and obsolete the need for this entire class.
+ *
+ * @deprecated This field only exists so the new status bar pipeline can interface with the
+ * view holder system.
+ */
+ @Deprecated
+ public static final int TYPE_WIFI_NEW = 4;
+
@IntDef({
TYPE_ICON,
TYPE_WIFI,
TYPE_MOBILE,
- TYPE_MOBILE_NEW
+ TYPE_MOBILE_NEW,
+ TYPE_WIFI_NEW
})
@Retention(RetentionPolicy.SOURCE)
@interface IconType {}
@@ -95,6 +108,13 @@
return holder;
}
+ /** Creates a new holder with for the new wifi icon. */
+ public static StatusBarIconHolder forNewWifiIcon() {
+ StatusBarIconHolder holder = new StatusBarIconHolder();
+ holder.mType = TYPE_WIFI_NEW;
+ return holder;
+ }
+
/** */
public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
StatusBarIconHolder holder = new StatusBarIconHolder();
@@ -172,9 +192,10 @@
case TYPE_MOBILE:
return mMobileState.visible;
case TYPE_MOBILE_NEW:
- //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ case TYPE_WIFI_NEW:
+ // The new pipeline controls visibilities via the view model and view binder, so
+ // this is effectively an unused return value.
return true;
-
default:
return true;
}
@@ -199,7 +220,9 @@
break;
case TYPE_MOBILE_NEW:
- //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+ case TYPE_WIFI_NEW:
+ // The new pipeline controls visibilities via the view model and view binder, so
+ // ignore setVisible.
break;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a797d3b..21b8762 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -171,7 +171,6 @@
if (mBouncerAnimating) {
mCentralSurfaces.setBouncerHiddenFraction(expansion);
}
- updateStates();
}
@Override
@@ -477,7 +476,6 @@
} else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
- && !mCentralSurfaces.isInLaunchTransition()
&& !isUnlockCollapsing()) {
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
@@ -846,21 +844,6 @@
if (isShowing && isOccluding) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
- if (mCentralSurfaces.isInLaunchTransition()) {
- final Runnable endRunnable = new Runnable() {
- @Override
- public void run() {
- mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
- reset(true /* hideBouncerWhenShowing */);
- }
- };
- mCentralSurfaces.fadeKeyguardAfterLaunchTransition(
- null /* beforeFading */,
- endRunnable,
- endRunnable);
- return;
- }
-
if (mCentralSurfaces.isLaunchingActivityOverLockscreen()) {
// When isLaunchingActivityOverLockscreen() is true, we know for sure that the post
// collapse runnables will be run.
@@ -900,7 +883,7 @@
} else {
mBouncerInteractor.startDisappearAnimation(finishRunnable);
}
- mCentralSurfaces.onBouncerPreHideAnimation();
+ mNotificationPanelViewController.startBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
// the keyguard. If there is no animation, we wait before updating the state so that we
@@ -932,8 +915,7 @@
long uptimeMillis = SystemClock.uptimeMillis();
long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
- if (mCentralSurfaces.isInLaunchTransition()
- || mKeyguardStateController.isFlingingToDismissKeyguard()) {
+ if (mKeyguardStateController.isFlingingToDismissKeyguard()) {
final boolean wasFlingingToDismissKeyguard =
mKeyguardStateController.isFlingingToDismissKeyguard();
mCentralSurfaces.fadeKeyguardAfterLaunchTransition(new Runnable() {
@@ -1309,7 +1291,7 @@
@Override
public boolean shouldDisableWindowAnimationsForUnlock() {
- return mCentralSurfaces.isInLaunchTransition();
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index ee948c0..b1642d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -31,7 +31,7 @@
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) {
- centralSurfaces.collapsePanelWithDuration(
+ centralSurfaces.notificationPanelViewController.collapseWithDuration(
ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 5cd2ba1..b6ae4a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -23,7 +23,6 @@
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.Context;
@@ -60,9 +59,8 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -126,7 +124,6 @@
Context context,
Handler mainThreadHandler,
Executor uiBgExecutor,
- NotifPipeline notifPipeline,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManagerPhone headsUpManager,
ActivityStarter activityStarter,
@@ -151,7 +148,8 @@
NotificationPresenter presenter,
NotificationPanelViewController panel,
ActivityLaunchAnimator activityLaunchAnimator,
- NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
+ NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
+ LaunchFullScreenIntentProvider launchFullScreenIntentProvider) {
mContext = context;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
@@ -182,12 +180,7 @@
mActivityLaunchAnimator = activityLaunchAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
- notifPipeline.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryAdded(NotificationEntry entry) {
- handleFullScreenIntent(entry);
- }
- });
+ launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
}
/**
@@ -549,38 +542,36 @@
}
@VisibleForTesting
- void handleFullScreenIntent(NotificationEntry entry) {
- if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
- if (shouldSuppressFullScreenIntent(entry)) {
- mLogger.logFullScreenIntentSuppressedByDnD(entry);
- } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
- mLogger.logFullScreenIntentNotImportantEnough(entry);
- } else {
- // Stop screensaver if the notification has a fullscreen intent.
- // (like an incoming phone call)
- mUiBgExecutor.execute(() -> {
- try {
- mDreamManager.awaken();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- });
+ void launchFullScreenIntent(NotificationEntry entry) {
+ // Skip if device is in VR mode.
+ if (mPresenter.isDeviceInVrMode()) {
+ mLogger.logFullScreenIntentSuppressedByVR(entry);
+ return;
+ }
- // not immersive & a fullscreen alert should be shown
- final PendingIntent fullscreenIntent =
- entry.getSbn().getNotification().fullScreenIntent;
- mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
- try {
- EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
- entry.getKey());
- mCentralSurfaces.wakeUpForFullScreenIntent();
- fullscreenIntent.send();
- entry.notifyFullScreenIntentLaunched();
- mMetricsLogger.count("note_fullscreen", 1);
- } catch (PendingIntent.CanceledException e) {
- // ignore
- }
+ // Stop screensaver if the notification has a fullscreen intent.
+ // (like an incoming phone call)
+ mUiBgExecutor.execute(() -> {
+ try {
+ mDreamManager.awaken();
+ } catch (RemoteException e) {
+ e.printStackTrace();
}
+ });
+
+ // not immersive & a fullscreen alert should be shown
+ final PendingIntent fullscreenIntent =
+ entry.getSbn().getNotification().fullScreenIntent;
+ mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
+ try {
+ EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+ entry.getKey());
+ mCentralSurfaces.wakeUpForFullScreenIntent();
+ fullscreenIntent.send();
+ entry.notifyFullScreenIntentLaunched();
+ mMetricsLogger.count("note_fullscreen", 1);
+ } catch (PendingIntent.CanceledException e) {
+ // ignore
}
}
@@ -607,12 +598,4 @@
mMainThreadHandler.post(mShadeController::collapsePanel);
}
}
-
- private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
- if (mPresenter.isDeviceInVrMode()) {
- return true;
- }
-
- return entry.shouldSuppressFullScreenIntent();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index 81edff4..1f0b96a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -96,19 +96,11 @@
})
}
- fun logFullScreenIntentSuppressedByDnD(entry: NotificationEntry) {
+ fun logFullScreenIntentSuppressedByVR(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
str1 = entry.logKey
}, {
- "No Fullscreen intent: suppressed by DND: $str1"
- })
- }
-
- fun logFullScreenIntentNotImportantEnough(entry: NotificationEntry) {
- buffer.log(TAG, DEBUG, {
- str1 = entry.logKey
- }, {
- "No Fullscreen intent: not important enough: $str1"
+ "No Fullscreen intent: suppressed by VR mode: $str1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 492734e..de7bf3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -212,7 +212,7 @@
private void updateWifiIconWithState(WifiIconState state) {
if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString());
if (state.visible && state.resId > 0) {
- mIconController.setSignalIcon(mSlotWifi, state);
+ mIconController.setWifiIcon(mSlotWifi, state);
mIconController.setIconVisibility(mSlotWifi, true);
} else {
mIconController.setIconVisibility(mSlotWifi, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index 6cd8c78..08599c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.phone
-import android.view.InsetsVisibilities
+import android.view.WindowInsets.Type.InsetsType
import android.view.WindowInsetsController.Appearance
import android.view.WindowInsetsController.Behavior
import com.android.internal.statusbar.LetterboxDetails
@@ -66,21 +66,21 @@
params.appearanceRegionsArray,
params.navbarColorManagedByIme,
params.behavior,
- params.requestedVisibilities,
+ params.requestedVisibleTypes,
params.packageName,
params.letterboxesArray)
}
}
fun onSystemBarAttributesChanged(
- displayId: Int,
- @Appearance originalAppearance: Int,
- originalAppearanceRegions: Array<AppearanceRegion>,
- navbarColorManagedByIme: Boolean,
- @Behavior behavior: Int,
- requestedVisibilities: InsetsVisibilities,
- packageName: String,
- letterboxDetails: Array<LetterboxDetails>
+ displayId: Int,
+ @Appearance originalAppearance: Int,
+ originalAppearanceRegions: Array<AppearanceRegion>,
+ navbarColorManagedByIme: Boolean,
+ @Behavior behavior: Int,
+ @InsetsType requestedVisibleTypes: Int,
+ packageName: String,
+ letterboxDetails: Array<LetterboxDetails>
) {
lastSystemBarAttributesParams =
SystemBarAttributesParams(
@@ -89,7 +89,7 @@
originalAppearanceRegions.toList(),
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails.toList())
@@ -104,7 +104,7 @@
centralSurfaces.updateBubblesVisibility()
statusBarStateController.setSystemBarAttributes(
- appearance, behavior, requestedVisibilities, packageName)
+ appearance, behavior, requestedVisibleTypes, packageName)
}
private fun modifyAppearanceIfNeeded(
@@ -137,14 +137,14 @@
* [SystemBarAttributesListener.onSystemBarAttributesChanged].
*/
private data class SystemBarAttributesParams(
- val displayId: Int,
- @Appearance val appearance: Int,
- val appearanceRegions: List<AppearanceRegion>,
- val navbarColorManagedByIme: Boolean,
- @Behavior val behavior: Int,
- val requestedVisibilities: InsetsVisibilities,
- val packageName: String,
- val letterboxes: List<LetterboxDetails>,
+ val displayId: Int,
+ @Appearance val appearance: Int,
+ val appearanceRegions: List<AppearanceRegion>,
+ val navbarColorManagedByIme: Boolean,
+ @Behavior val behavior: Int,
+ @InsetsType val requestedVisibleTypes: Int,
+ val packageName: String,
+ val letterboxes: List<LetterboxDetails>,
) {
val letterboxesArray = letterboxes.toTypedArray()
val appearanceRegionsArray = appearanceRegions.toTypedArray()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 06cd12d..946d7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -27,11 +27,26 @@
/** True if we should display the mobile icons using the new status bar data pipeline. */
fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
+ /**
+ * True if we should run the new mobile icons backend to get the logging.
+ *
+ * Does *not* affect whether we render the mobile icons using the new backend data. See
+ * [useNewMobileIcons] for that.
+ */
+ fun runNewMobileIconsBackend(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
+
/** True if we should display the wifi icon using the new status bar data pipeline. */
fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
- // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the
- // logging without getting the UI effects.
+ /**
+ * True if we should run the new wifi icon backend to get the logging.
+ *
+ * Does *not* affect whether we render the wifi icon using the new backend data. See
+ * [useNewWifiIcon] for that.
+ */
+ fun runNewWifiIconBackend(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
/**
* Returns true if we should apply some coloring to the wifi icon that was rendered with the new
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 380017c..c7e0ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import javax.inject.Inject
@@ -50,6 +51,7 @@
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
@Application scope: CoroutineScope,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
) {
private val mobileSubIds: Flow<List<Int>> =
interactor.filteredSubscriptions.mapLatest { infos ->
@@ -66,8 +68,14 @@
private val mobileSubIdsState: StateFlow<List<Int>> =
mobileSubIds
.onEach {
- // Notify the icon controller here so that it knows to add icons
- iconController.setNewMobileIconSubIds(it)
+ // Only notify the icon controller if we want to *render* the new icons.
+ // Note that this flow may still run if
+ // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+ // get the logging data without rendering.
+ if (statusBarPipelineFlags.useNewMobileIcons()) {
+ // Notify the icon controller here so that it knows to add icons
+ iconController.setNewMobileIconSubIds(it)
+ }
}
.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
new file mode 100644
index 0000000..b816364
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui
+
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * This class serves as a bridge between the old UI classes and the new data pipeline.
+ *
+ * Once the new pipeline notifies [wifiViewModel] that the wifi icon should be visible, this class
+ * notifies [iconController] to inflate the wifi icon (if needed). After that, the [wifiViewModel]
+ * has sole responsibility for updating the wifi icon drawable, visibility, etc. and the
+ * [iconController] will not do any updates to the icon.
+ */
+@SysUISingleton
+class WifiUiAdapter
+@Inject
+constructor(
+ private val iconController: StatusBarIconController,
+ private val wifiViewModel: WifiViewModel,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) {
+ /**
+ * Binds the container for all the status bar icons to a view model, so that we inflate the wifi
+ * view once we receive a valid icon from the data pipeline.
+ *
+ * NOTE: This should go away as we better integrate the data pipeline with the UI.
+ *
+ * @return the view model used for this particular group in the given [location].
+ */
+ fun bindGroup(
+ statusBarIconGroup: ViewGroup,
+ location: StatusBarLocation,
+ ): LocationBasedWifiViewModel {
+ val locationViewModel =
+ when (location) {
+ StatusBarLocation.HOME -> wifiViewModel.home
+ StatusBarLocation.KEYGUARD -> wifiViewModel.keyguard
+ StatusBarLocation.QS -> wifiViewModel.qs
+ }
+
+ statusBarIconGroup.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ locationViewModel.wifiIcon.collect { wifiIcon ->
+ // Only notify the icon controller if we want to *render* the new icon.
+ // Note that this flow may still run if
+ // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
+ // want to get the logging data without rendering.
+ if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+ iconController.setNewWifiIcon()
+ }
+ }
+ }
+ }
+ }
+
+ return locationViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 25537b9..345f8cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,9 +30,7 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
@@ -62,26 +60,9 @@
fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
}
- /**
- * Binds the view to the appropriate view-model based on the given location. The view will
- * continue to be updated following updates from the view-model.
- */
- @JvmStatic
- fun bind(
- view: ViewGroup,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
- ): Binding {
- return when (location) {
- StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
- StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
- StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
- }
- }
-
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
- private fun bind(
+ fun bind(
view: ViewGroup,
viewModel: LocationBasedWifiViewModel,
): Binding {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 0cd9bd7..a45076b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -26,9 +26,8 @@
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
/**
* A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that
@@ -81,12 +80,11 @@
private fun initView(
slotName: String,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
+ wifiViewModel: LocationBasedWifiViewModel,
) {
slot = slotName
initDotView()
- binding = WifiViewBinder.bind(this, wifiViewModel, location)
+ binding = WifiViewBinder.bind(this, wifiViewModel)
}
// Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
@@ -116,14 +114,13 @@
fun constructAndBind(
context: Context,
slot: String,
- wifiViewModel: WifiViewModel,
- location: StatusBarLocation,
+ wifiViewModel: LocationBasedWifiViewModel,
): ModernStatusBarWifiView {
return (
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView
).also {
- it.initView(slot, wifiViewModel, location)
+ it.initView(slot, wifiViewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 89b96b7..0782bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -145,7 +145,8 @@
else -> null
}
}
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+ .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<WifiActivityModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 1d414745..7acdaff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -118,7 +118,10 @@
private void updateDeviceState(int state) {
Log.v(TAG, "updateDeviceState [state=" + state + "]");
- Trace.beginSection("updateDeviceState [state=" + state + "]");
+ if (Trace.isEnabled()) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
+ }
try {
if (mDeviceState == state) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index bd2123a..69b55c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -33,6 +33,7 @@
import androidx.annotation.NonNull;
import com.android.internal.util.ConcurrentUtils;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -63,6 +64,7 @@
private volatile int mNumConnectedDevices;
// Assume tethering is available until told otherwise
private volatile boolean mIsTetheringSupported = true;
+ private final boolean mIsTetheringSupportedConfig;
private volatile boolean mHasTetherableWifiRegexs = true;
private boolean mWaitingForTerminalState;
@@ -100,23 +102,29 @@
mTetheringManager = context.getSystemService(TetheringManager.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mMainHandler = mainHandler;
- mTetheringManager.registerTetheringEventCallback(
- new HandlerExecutor(backgroundHandler), mTetheringCallback);
+ mIsTetheringSupportedConfig = context.getResources()
+ .getBoolean(R.bool.config_show_wifi_tethering);
+ if (mIsTetheringSupportedConfig) {
+ mTetheringManager.registerTetheringEventCallback(
+ new HandlerExecutor(backgroundHandler), mTetheringCallback);
+ }
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
/**
* Whether hotspot is currently supported.
*
- * This will return {@code true} immediately on creation of the controller, but may be updated
- * later. Callbacks from this controllers will notify if the state changes.
+ * This may return {@code true} immediately on creation of the controller, but may be updated
+ * later as capabilities are collected from System Server.
+ *
+ * Callbacks from this controllers will notify if the state changes.
*
* @return {@code true} if hotspot is supported (or we haven't been told it's not)
* @see #addCallback
*/
@Override
public boolean isHotspotSupported() {
- return mIsTetheringSupported && mHasTetherableWifiRegexs
+ return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
&& UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
index f3d183c..9db207a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
@@ -78,7 +78,8 @@
synchronized (mListeners) {
mListeners.add(listener);
if (mListeners.size() == 1) {
- mContext.registerReceiver(mPermControllerChangeReceiver, PKG_CHANGE_INTENT_FILTER);
+ mContext.registerReceiver(mPermControllerChangeReceiver, PKG_CHANGE_INTENT_FILTER,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
mBgHandler.post(() -> {
mSafetyCenterEnabled = mSafetyCenterManager.isSafetyCenterEnabled();
listener.onSafetyCenterEnableChanged(isSafetyCenterEnabled());
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index f9d14cd..a374885 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -28,6 +28,8 @@
import android.view.WindowManagerGlobal
import android.widget.Toast
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -58,6 +60,8 @@
private val devicePolicyManager: DevicePolicyManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
private val uiEventLogger: UiEventLogger,
+ resumeSessionReceiver: GuestResumeSessionReceiver,
+ resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver,
) {
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
@@ -65,6 +69,11 @@
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean = repository.isGuestUserResetting
+ init {
+ resumeSessionReceiver.register()
+ resetOrExitSessionReceiver.register()
+ }
+
/** Notifies that the device has finished booting. */
fun onDeviceBootCompleted() {
applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 4e77514..a4384d5 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -25,6 +25,7 @@
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+import static com.android.systemui.flags.Flags.WM_BUBBLE_BAR;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -51,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.system.QuickStepContract;
@@ -129,6 +131,7 @@
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
+ FeatureFlags featureFlags,
Executor sysuiMainExecutor) {
if (bubblesOptional.isPresent()) {
return new BubblesManager(context,
@@ -146,6 +149,7 @@
notifCollection,
notifPipeline,
sysUiState,
+ featureFlags,
sysuiMainExecutor);
} else {
return null;
@@ -168,6 +172,7 @@
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
+ FeatureFlags featureFlags,
Executor sysuiMainExecutor) {
mContext = context;
mBubbles = bubbles;
@@ -352,6 +357,7 @@
});
}
};
+ mBubbles.setBubbleBarEnabled(featureFlags.isEnabled(WM_BUBBLE_BAR));
mBubbles.setSysuiProxy(mSysuiProxy);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c0..131cf7d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -72,6 +72,7 @@
keyguardOccluded = false,
occludingAppRequestingFp = false,
primaryUser = false,
+ shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
switchingUser = false,
udfps = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 52f8ef8..0a2b3d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -194,6 +194,15 @@
}
@Test
+ public void onInitConfiguresViewMode() {
+ mKeyguardSecurityContainerController.onInit();
+ verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+ eq(mUserSwitcherController),
+ any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+ eq(mFalsingA11yDelegate));
+ }
+
+ @Test
public void showSecurityScreen_canInflateAllModes() {
SecurityMode[] modes = SecurityMode.values();
for (SecurityMode mode : modes) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 680c3b8..5718835 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -21,6 +21,7 @@
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -39,6 +40,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -54,6 +56,7 @@
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -61,18 +64,21 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -111,6 +117,7 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Assert;
@@ -182,6 +189,8 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
+ private SecureSettings mSecureSettings;
+ @Mock
private TelephonyManager mTelephonyManager;
@Mock
private SensorPrivacyManager mSensorPrivacyManager;
@@ -215,6 +224,7 @@
private GlobalSettings mGlobalSettings;
private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -224,6 +234,9 @@
@Captor
private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
+ @Mock
+ private Uri mURI;
+
// Direct executor
private final Executor mBackgroundExecutor = Runnable::run;
private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
+
+ when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
+
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ ExtendedMockito.spyOn(contentResolver);
+ doNothing().when(contentResolver)
+ .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+ anyInt());
+
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
verify(mBiometricManager)
@@ -1136,6 +1158,63 @@
}
@Test
+ public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+ throws RemoteException {
+ // SFPS supported and enrolled
+ final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+ // WHEN require screen on to auth is disabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+
+ // Preconditions for sfps auth to run
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ biometricsEnabledForCurrentUser();
+ userNotCurrentlySwitching();
+
+ statusBarShadeIsLocked();
+ mTestableLooper.processAllMessages();
+
+ // THEN we should listen for sfps when screen off, because require screen on is disabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+ // WHEN require screen on to auth is enabled, and keyguard is not awake
+ when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
+ mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+ // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+
+ // Device now awake & keyguard is now interactive
+ deviceNotGoingToSleep();
+ deviceIsInteractive();
+ keyguardIsVisible();
+
+ // THEN we should listen for sfps when screen on, and require screen on is enabled
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+ }
+
+ private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
+ @FingerprintSensorProperties.SensorType int sensorType) {
+ return new FingerprintSensorPropertiesInternal(
+ 0 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ new ArrayList<ComponentInfoInternal>(),
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ }
+
+ @Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
@@ -1804,7 +1883,7 @@
protected TestableKeyguardUpdateMonitor(Context context) {
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
- mBroadcastDispatcher, mDumpManager,
+ mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
mStatusBarStateController, mLockPatternUtils,
mAuthController, mTelephonyListenerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 41c307a..aa14a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -20,6 +20,8 @@
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -108,9 +110,6 @@
@RunWithLooper(setAsMainLooper = true)
public class UdfpsControllerTest extends SysuiTestCase {
- // Use this for inputs going into SystemUI. Use UdfpsController.mUdfpsSensorId for things
- // leaving SystemUI.
- private static final int TEST_UDFPS_SENSOR_ID = 1;
private static final long TEST_REQUEST_ID = 70;
@Rule
@@ -121,7 +120,6 @@
// Dependencies
private FakeExecutor mBiometricsExecutor;
- private Execution mExecution;
@Mock
private LayoutInflater mLayoutInflater;
@Mock
@@ -197,17 +195,24 @@
private BouncerInteractor mBouncerInteractor;
// Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
+ @Captor
+ private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
private IUdfpsOverlayController mOverlayController;
- @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
- @Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
- @Captor private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
- @Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
+ @Captor
+ private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
+ @Captor
+ private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
+ @Captor
+ private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
+ @Captor
+ private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
private ScreenLifecycle.Observer mScreenObserver;
+ private FingerprintSensorPropertiesInternal mOpticalProps;
+ private FingerprintSensorPropertiesInternal mUltrasonicProps;
@Before
public void setUp() {
- mExecution = new FakeExecution();
+ Execution execution = new FakeExecution();
when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
.thenReturn(mUdfpsView);
@@ -220,9 +225,7 @@
when(mLayoutInflater.inflate(R.layout.udfps_fpm_other_view, null))
.thenReturn(mFpmOtherView);
when(mEnrollView.getContext()).thenReturn(mContext);
- when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
- final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
@@ -232,13 +235,25 @@
"" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
"vendor/version/revision" /* softwareVersion */));
- props.add(new FingerprintSensorPropertiesInternal(TEST_UDFPS_SENSOR_ID,
+ mOpticalProps = new FingerprintSensorPropertiesInternal(1 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequiresHardwareAuthToken */));
+ true /* resetLockoutRequiresHardwareAuthToken */);
+
+ mUltrasonicProps = new FingerprintSensorPropertiesInternal(2 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+
+ List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(mOpticalProps);
+ props.add(mUltrasonicProps);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+
mFgExecutor = new FakeExecutor(new FakeSystemClock());
// Create a fake background executor.
@@ -246,7 +261,7 @@
mUdfpsController = new UdfpsController(
mContext,
- mExecution,
+ execution,
mLayoutInflater,
mFingerprintManager,
mWindowManager,
@@ -281,13 +296,13 @@
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
mScreenObserver = mScreenObserverCaptor.getValue();
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, new UdfpsOverlayParams());
+ mUdfpsController.updateOverlayParams(mOpticalProps, new UdfpsOverlayParams());
mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode);
}
@Test
public void dozeTimeTick() throws RemoteException {
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
mUdfpsController.dozeTimeTick();
@@ -302,7 +317,7 @@
when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -337,7 +352,7 @@
when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -345,7 +360,7 @@
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
if (stale) {
- mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
mFgExecutor.runAllReady();
}
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
@@ -365,7 +380,7 @@
when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -388,12 +403,12 @@
@Test
public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
// GIVEN overlay was showing and the udfps bouncer is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
// WHEN the overlay is hidden
- mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
mFgExecutor.runAllReady();
// THEN the udfps bouncer is reset
@@ -402,13 +417,13 @@
@Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong());
- mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
mFgExecutor.runAllReady();
verify(mDisplayManager).unregisterDisplayListener(any());
@@ -438,12 +453,12 @@
}
// Initialize the overlay with old parameters.
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, oldParams);
+ mUdfpsController.updateOverlayParams(mOpticalProps, oldParams);
// Show the overlay.
reset(mWindowManager);
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID,
- TEST_UDFPS_SENSOR_ID,
+ mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_ENROLL_ENROLLING,
mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -451,7 +466,7 @@
// Update overlay parameters.
reset(mWindowManager);
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, newParams);
+ mUdfpsController.updateOverlayParams(mOpticalProps, newParams);
mFgExecutor.runAllReady();
// Ensure the overlay was recreated.
@@ -473,18 +488,18 @@
final int rotation = Surface.ROTATION_0;
// Initialize the overlay.
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, rotation));
// Show the overlay.
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mWindowManager).addView(any(), any());
// Update overlay with the same parameters.
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, rotation));
mFgExecutor.runAllReady();
@@ -526,13 +541,13 @@
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// Show the overlay.
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
// Test ROTATION_0
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_0));
MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
@@ -549,7 +564,7 @@
// Test ROTATION_90
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_90));
event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
@@ -565,7 +580,7 @@
// Test ROTATION_270
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_270));
event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
@@ -581,7 +596,7 @@
// Test ROTATION_180
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
scaleFactor, Surface.ROTATION_180));
// ROTATION_180 is not supported. It should be treated like ROTATION_0.
@@ -597,63 +612,108 @@
eq(expectedY), eq(expectedMinor), eq(expectedMajor));
}
+ private void runForAllUdfpsTypes(
+ ThrowingConsumer<FingerprintSensorPropertiesInternal> sensorPropsConsumer) {
+ for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
+ mUltrasonicProps)) {
+ mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams());
+ sensorPropsConsumer.accept(sensorProps);
+ }
+ }
+
@Test
- public void fingerDown() throws RemoteException {
+ public void fingerDown() {
+ runForAllUdfpsTypes(this::fingerDownForSensor);
+ }
+
+ private void fingerDownForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker,
+ mKeyguardUpdateMonitor);
+
// Configure UdfpsView to accept the ACTION_DOWN event
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
- // WHEN ACTION_DOWN is received
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN ACTION_DOWN is received
MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
mBiometricsExecutor.runAllReady();
downEvent.recycle();
- MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
- // FIX THIS TEST
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
mBiometricsExecutor.runAllReady();
moveEvent.recycle();
+
mFgExecutor.runAllReady();
+
// THEN FingerprintManager is notified about onPointerDown
verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
eq(0f));
verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
anyFloat(), anyFloat());
- verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+
// AND display configuration begins
- verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+ } else {
+ verify(mLatencyTracker, never()).onActionStart(
+ eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ verify(mUdfpsView, never()).configureDisplay(any());
+ }
verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
- // AND onDisplayConfigured notifies FingerprintManager about onUiReady
- mOnDisplayConfiguredCaptor.getValue().run();
- mBiometricsExecutor.runAllReady();
- InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
- inOrder.verify(mAlternateTouchProvider).onUiReady();
- inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+ mOnDisplayConfiguredCaptor.getValue().run();
+ mBiometricsExecutor.runAllReady();
+ InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
+ inOrder.verify(mAlternateTouchProvider).onUiReady();
+ inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ } else {
+ verify(mAlternateTouchProvider, never()).onUiReady();
+ verify(mLatencyTracker, never()).onActionEnd(
+ eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ }
}
@Test
- public void aodInterrupt() throws RemoteException {
+ public void aodInterrupt() {
+ runForAllUdfpsTypes(this::aodInterruptForSensor);
+ }
+
+ private void aodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ mUdfpsController.cancelAodInterrupt();
+ reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor);
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
// GIVEN that the overlay is showing and screen is on and fp is running
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
mFgExecutor.runAllReady();
- // THEN display configuration begins
- // AND onDisplayConfigured notifies FingerprintManager about onUiReady
- verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
- mOnDisplayConfiguredCaptor.getValue().run();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // THEN display configuration begins
+ // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+ verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+ mOnDisplayConfiguredCaptor.getValue().run();
+ } else {
+ verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+ }
mBiometricsExecutor.runAllReady();
verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID),
eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */);
@@ -663,54 +723,92 @@
}
@Test
- public void cancelAodInterrupt() throws RemoteException {
+ public void cancelAodInterrupt() {
+ runForAllUdfpsTypes(this::cancelAodInterruptForSensor);
+ }
+
+ private void cancelAodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ reset(mUdfpsView);
+
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
- // WHEN it is cancelled
- mUdfpsController.cancelAodInterrupt();
- // THEN the display is unconfigured
- verify(mUdfpsView).unconfigureDisplay();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ // WHEN it is cancelled
+ mUdfpsController.cancelAodInterrupt();
+ // THEN the display is unconfigured
+ verify(mUdfpsView).unconfigureDisplay();
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ // WHEN it is cancelled
+ mUdfpsController.cancelAodInterrupt();
+ // THEN the display configuration is unchanged.
+ verify(mUdfpsView, never()).unconfigureDisplay();
+ }
}
@Test
- public void aodInterruptTimeout() throws RemoteException {
+ public void aodInterruptTimeout() {
+ runForAllUdfpsTypes(this::aodInterruptTimeoutForSensor);
+ }
+
+ private void aodInterruptTimeoutForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ reset(mUdfpsView);
+
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- // THEN the display is unconfigured
- verify(mUdfpsView).unconfigureDisplay();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // THEN the display is unconfigured.
+ verify(mUdfpsView).unconfigureDisplay();
+ } else {
+ // THEN the display configuration is unchanged.
+ verify(mUdfpsView, never()).unconfigureDisplay();
+ }
}
@Test
- public void aodInterruptCancelTimeoutActionOnFingerUp() throws RemoteException {
+ public void aodInterruptCancelTimeoutActionOnFingerUp() {
+ runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnFingerUpForSensor);
+ }
+
+ private void aodInterruptCancelTimeoutActionOnFingerUpForSensor(
+ FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ reset(mUdfpsView);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- // Configure UdfpsView to accept the ACTION_UP event
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // Configure UdfpsView to accept the ACTION_UP event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN ACTION_UP is received
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -735,37 +833,54 @@
moveEvent.recycle();
mFgExecutor.runAllReady();
- // Configure UdfpsView to accept the finger up event
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // Configure UdfpsView to accept the finger up event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- // THEN the display should be unconfigured once. If the timeout action is not
- // cancelled, the display would be unconfigured twice which would cause two
- // FP attempts.
- verify(mUdfpsView, times(1)).unconfigureDisplay();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // THEN the display should be unconfigured once. If the timeout action is not
+ // cancelled, the display would be unconfigured twice which would cause two
+ // FP attempts.
+ verify(mUdfpsView, times(1)).unconfigureDisplay();
+ } else {
+ verify(mUdfpsView, never()).unconfigureDisplay();
+ }
}
@Test
- public void aodInterruptCancelTimeoutActionOnAcquired() throws RemoteException {
+ public void aodInterruptCancelTimeoutActionOnAcquired() {
+ runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnAcquiredForSensor);
+ }
+
+ private void aodInterruptCancelTimeoutActionOnAcquiredForSensor(
+ FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ reset(mUdfpsView);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- // Configure UdfpsView to accept the acquired event
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // Configure UdfpsView to accept the acquired event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN acquired is received
- mOverlayController.onAcquired(TEST_UDFPS_SENSOR_ID,
+ mOverlayController.onAcquired(sensorProps.sensorId,
BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
// Configure UdfpsView to accept the ACTION_DOWN event
@@ -785,29 +900,43 @@
moveEvent.recycle();
mFgExecutor.runAllReady();
- // Configure UdfpsView to accept the finger up event
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // Configure UdfpsView to accept the finger up event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- // THEN the display should be unconfigured once. If the timeout action is not
- // cancelled, the display would be unconfigured twice which would cause two
- // FP attempts.
- verify(mUdfpsView, times(1)).unconfigureDisplay();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // THEN the display should be unconfigured once. If the timeout action is not
+ // cancelled, the display would be unconfigured twice which would cause two
+ // FP attempts.
+ verify(mUdfpsView, times(1)).unconfigureDisplay();
+ } else {
+ verify(mUdfpsView, never()).unconfigureDisplay();
+ }
}
@Test
- public void aodInterruptScreenOff() throws RemoteException {
+ public void aodInterruptScreenOff() {
+ runForAllUdfpsTypes(this::aodInterruptScreenOffForSensor);
+ }
+
+ private void aodInterruptScreenOffForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ reset(mUdfpsView);
+
// GIVEN screen off
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOff();
mFgExecutor.runAllReady();
// WHEN aod interrupt is received
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// THEN display doesn't get configured because it's off
@@ -815,9 +944,16 @@
}
@Test
- public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+ public void aodInterrupt_fingerprintNotRunning() {
+ runForAllUdfpsTypes(this::aodInterrupt_fingerprintNotRunningForSensor);
+ }
+
+ private void aodInterrupt_fingerprintNotRunningForSensor(
+ FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ reset(mUdfpsView);
+
// GIVEN showing overlay
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
@@ -839,7 +975,7 @@
// GIVEN that the overlay is showing and a11y touch exploration enabled
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -874,7 +1010,7 @@
// GIVEN that the overlay is showing and a11y touch exploration NOT enabled
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
index 7864f21b..1bc237d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
@@ -23,7 +23,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -48,7 +48,7 @@
@Mock
private AuthController mAuthController;
@Mock
- private IUdfpsHbmListener mDisplayCallback;
+ private IUdfpsRefreshRateRequestCallback mDisplayCallback;
@Mock
private UdfpsLogger mUdfpsLogger;
@Mock
@@ -68,7 +68,7 @@
when(contextSpy.getDisplayId()).thenReturn(DISPLAY_ID);
// Set up mocks.
- when(mAuthController.getUdfpsHbmListener()).thenReturn(mDisplayCallback);
+ when(mAuthController.getUdfpsRefreshRateCallback()).thenReturn(mDisplayCallback);
// Create a real controller with mock dependencies.
mHbmController = new UdfpsDisplayMode(contextSpy, mExecution, mAuthController,
@@ -81,7 +81,7 @@
mHbmController.enable(mOnEnabled);
// Should set the appropriate refresh rate for UDFPS and notify the caller.
- verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
verify(mOnEnabled).run();
// Disable the UDFPS mode.
@@ -89,7 +89,7 @@
// Should unset the refresh rate and notify the caller.
verify(mOnDisabled).run();
- verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID));
}
@Test
@@ -98,7 +98,7 @@
mHbmController.enable(mOnEnabled);
// Should set the appropriate refresh rate for UDFPS and notify the caller.
- verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
verify(mOnEnabled).run();
// Second request to enable the UDFPS mode, while it's still enabled.
@@ -115,7 +115,7 @@
mHbmController.enable(mOnEnabled);
// Should set the appropriate refresh rate for UDFPS and notify the caller.
- verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
verify(mOnEnabled).run();
// First request to disable the UDFPS mode.
@@ -123,7 +123,7 @@
// Should unset the refresh rate and notify the caller.
verify(mOnDisabled).run();
- verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+ verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID));
// Second request to disable the UDFPS mode, when it's already disabled.
mHbmController.disable(mOnDisabled);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index c313c8d..e5c7a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -16,15 +16,7 @@
package com.android.systemui.biometrics;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -44,7 +36,6 @@
import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -55,7 +46,6 @@
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
-import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -114,351 +104,6 @@
mController = createUdfpsKeyguardViewController();
}
- @Test
- public void testRegistersExpansionChangedListenerOnAttached() {
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- }
-
- @Test
- public void testRegistersStatusBarStateListenersOnAttached() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
- }
-
- @Test
- public void testViewControllerQueriesSBStateOnAttached() {
- mController.onViewAttached();
- verify(mStatusBarStateController).getState();
- verify(mStatusBarStateController).getDozeAmount();
-
- final float dozeAmount = .88f;
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(dozeAmount);
- captureStatusBarStateListeners();
-
- mController.onViewAttached();
- verify(mView, atLeast(1)).setPauseAuth(true);
- verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount,
- UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
- }
-
- @Test
- public void testListenersUnregisteredOnDetached() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
- captureStatusBarExpansionListeners();
- captureKeyguardStateControllerCallback();
- mController.onViewDetached();
-
- verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
- for (ShadeExpansionListener listener : mExpansionListeners) {
- verify(mShadeExpansionStateManager).removeExpansionListener(listener);
- }
- verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
- }
-
- @Test
- public void testDozeEventsSentToView() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
-
- final float linear = .55f;
- final float eased = .65f;
- mStatusBarStateListener.onDozeAmountChanged(linear, eased);
-
- verify(mView).onDozeAmountChanged(linear, eased,
- UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
- }
-
- @Test
- public void testShouldPauseAuthUnpausedAlpha0() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
-
- when(mView.getUnpausedAlpha()).thenReturn(0);
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
- public void testFadeFromDialogSuggestedAlpha() {
- // GIVEN view is attached and status bar expansion is 1f
- mController.onViewAttached();
- captureStatusBarStateListeners();
- captureStatusBarExpansionListeners();
- updateStatusBarExpansion(1f, true);
- reset(mView);
-
- // WHEN dialog suggested alpha is .6f
- when(mView.getDialogSuggestedAlpha()).thenReturn(.6f);
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- // THEN alpha is updated based on dialog suggested alpha
- verify(mView).setUnpausedAlpha((int) (.6f * 255));
- }
-
- @Test
- public void testShouldNotPauseAuthOnKeyguard() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
-
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- assertFalse(mController.shouldPauseAuth());
- }
-
- @Test
- public void testShouldPauseAuthIsLaunchTransitionFadingAway() {
- // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
- mController.onViewAttached();
- captureStatusBarStateListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- // WHEN isLaunchTransitionFadingAway=true
- captureKeyguardStateControllerCallback();
- when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true);
- mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged();
-
- // THEN pause auth
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
- public void testShouldPauseAuthOnShadeLocked() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
-
- sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
-
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
- public void testShouldPauseAuthOnShade() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
-
- // WHEN not on keyguard yet (shade = home)
- sendStatusBarStateChanged(StatusBarState.SHADE);
-
- // THEN pause auth
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
- public void testShouldPauseAuthAnimatingScreenOffFromShade() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
-
- // WHEN transitioning from home/shade => keyguard + animating screen off
- mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD);
- when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true);
-
- // THEN pause auth
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
- public void testDoNotPauseAuthAnimatingScreenOffFromLS() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
-
- // WHEN animating screen off transition from LS => AOD
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
- when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true);
-
- // THEN don't pause auth
- assertFalse(mController.shouldPauseAuth());
- }
-
- @Test
- public void testOverrideShouldPauseAuthOnShadeLocked() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
- captureAltAuthInterceptor();
-
- sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
- assertTrue(mController.shouldPauseAuth());
-
- mAltAuthInterceptor.showAlternateAuthBouncer(); // force show
- assertFalse(mController.shouldPauseAuth());
- assertTrue(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
-
- mAltAuthInterceptor.hideAlternateAuthBouncer(); // stop force show
- assertTrue(mController.shouldPauseAuth());
- assertFalse(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
- }
-
- @Test
- public void testOnDetachedStateReset() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureAltAuthInterceptor();
-
- // WHEN view is detached
- mController.onViewDetached();
-
- // THEN remove alternate auth interceptor
- verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
- }
-
- @Test
- public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureAltAuthInterceptor();
-
- // GIVEN udfps bouncer isn't showing
- mAltAuthInterceptor.hideAlternateAuthBouncer();
-
- // WHEN touch is observed outside the view
- mController.onTouchOutsideView();
-
- // THEN bouncer / alt auth methods are never called
- verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
- }
-
- @Test
- public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureAltAuthInterceptor();
-
- // GIVEN udfps bouncer is showing
- mAltAuthInterceptor.showAlternateAuthBouncer();
-
- // WHEN touch is observed outside the view 200ms later (just within threshold)
- mSystemClock.advanceTime(200);
- mController.onTouchOutsideView();
-
- // THEN bouncer / alt auth methods are never called because not enough time has passed
- verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
- verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
- }
-
- @Test
- public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureAltAuthInterceptor();
-
- // GIVEN udfps bouncer is showing
- mAltAuthInterceptor.showAlternateAuthBouncer();
-
- // WHEN touch is observed outside the view 205ms later
- mSystemClock.advanceTime(205);
- mController.onTouchOutsideView();
-
- // THEN show the bouncer
- verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
- }
-
- @Test
- public void testFadeInWithStatusBarExpansion() {
- // GIVEN view is attached
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- captureKeyguardStateControllerCallback();
- reset(mView);
-
- // WHEN status bar expansion is 0
- updateStatusBarExpansion(0, true);
-
- // THEN alpha is 0
- verify(mView).setUnpausedAlpha(0);
- }
-
- @Test
- public void testShowUdfpsBouncer() {
- // GIVEN view is attached and status bar expansion is 0
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- captureKeyguardStateControllerCallback();
- captureAltAuthInterceptor();
- updateStatusBarExpansion(0, true);
- reset(mView);
- when(mView.getContext()).thenReturn(mResourceContext);
- when(mResourceContext.getString(anyInt())).thenReturn("test string");
-
- // WHEN status bar expansion is 0 but udfps bouncer is requested
- mAltAuthInterceptor.showAlternateAuthBouncer();
-
- // THEN alpha is 255
- verify(mView).setUnpausedAlpha(255);
- }
-
- @Test
- public void testTransitionToFullShadeProgress() {
- // GIVEN view is attached and status bar expansion is 1f
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- updateStatusBarExpansion(1f, true);
- reset(mView);
- when(mView.getDialogSuggestedAlpha()).thenReturn(1f);
-
- // WHEN we're transitioning to the full shade
- float transitionProgress = .6f;
- mController.setTransitionToFullShadeProgress(transitionProgress);
-
- // THEN alpha is between 0 and 255
- verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
- }
-
- @Test
- public void testShowUdfpsBouncer_transitionToFullShadeProgress() {
- // GIVEN view is attached and status bar expansion is 1f
- mController.onViewAttached();
- captureStatusBarExpansionListeners();
- captureKeyguardStateControllerCallback();
- captureAltAuthInterceptor();
- updateStatusBarExpansion(1f, true);
- mAltAuthInterceptor.showAlternateAuthBouncer();
- reset(mView);
-
- // WHEN we're transitioning to the full shade
- mController.setTransitionToFullShadeProgress(1.0f);
-
- // THEN alpha is 255 (b/c udfps bouncer is requested)
- verify(mView).setUnpausedAlpha(255);
- }
-
- @Test
- public void testUpdatePanelExpansion_pauseAuth() {
- // GIVEN view is attached + on the keyguard
- mController.onViewAttached();
- captureStatusBarStateListeners();
- captureStatusBarExpansionListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
- reset(mView);
-
- // WHEN panelViewExpansion changes to hide
- when(mView.getUnpausedAlpha()).thenReturn(0);
- updateStatusBarExpansion(0f, false);
-
- // THEN pause auth is updated to PAUSE
- verify(mView, atLeastOnce()).setPauseAuth(true);
- }
-
- @Test
- public void testUpdatePanelExpansion_unpauseAuth() {
- // GIVEN view is attached + on the keyguard + panel expansion is 0f
- mController.onViewAttached();
- captureStatusBarStateListeners();
- captureStatusBarExpansionListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
- reset(mView);
-
- // WHEN panelViewExpansion changes to expanded
- when(mView.getUnpausedAlpha()).thenReturn(255);
- updateStatusBarExpansion(1f, true);
-
- // THEN pause auth is updated to NOT pause
- verify(mView, atLeastOnce()).setPauseAuth(false);
- }
-
protected void sendStatusBarStateChanged(int statusBarState) {
mStatusBarStateListener.onStateChanged(statusBarState);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index a067e43..55b6194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -16,7 +16,15 @@
package com.android.systemui.biometrics;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -25,6 +33,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
@@ -60,6 +69,353 @@
assertTrue(mController.shouldPauseAuth());
}
+
+
+ @Test
+ public void testRegistersExpansionChangedListenerOnAttached() {
+ mController.onViewAttached();
+ captureStatusBarExpansionListeners();
+ }
+
+ @Test
+ public void testRegistersStatusBarStateListenersOnAttached() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ }
+
+ @Test
+ public void testViewControllerQueriesSBStateOnAttached() {
+ mController.onViewAttached();
+ verify(mStatusBarStateController).getState();
+ verify(mStatusBarStateController).getDozeAmount();
+
+ final float dozeAmount = .88f;
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(dozeAmount);
+ captureStatusBarStateListeners();
+
+ mController.onViewAttached();
+ verify(mView, atLeast(1)).setPauseAuth(true);
+ verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount,
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
+ }
+
+ @Test
+ public void testListenersUnregisteredOnDetached() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ mController.onViewDetached();
+
+ verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
+ for (ShadeExpansionListener listener : mExpansionListeners) {
+ verify(mShadeExpansionStateManager).removeExpansionListener(listener);
+ }
+ verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
+ }
+
+ @Test
+ public void testDozeEventsSentToView() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+
+ final float linear = .55f;
+ final float eased = .65f;
+ mStatusBarStateListener.onDozeAmountChanged(linear, eased);
+
+ verify(mView).onDozeAmountChanged(linear, eased,
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
+ }
+
+ @Test
+ public void testShouldPauseAuthUnpausedAlpha0() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+
+ when(mView.getUnpausedAlpha()).thenReturn(0);
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testFadeFromDialogSuggestedAlpha() {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ updateStatusBarExpansion(1f, true);
+ reset(mView);
+
+ // WHEN dialog suggested alpha is .6f
+ when(mView.getDialogSuggestedAlpha()).thenReturn(.6f);
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ // THEN alpha is updated based on dialog suggested alpha
+ verify(mView).setUnpausedAlpha((int) (.6f * 255));
+ }
+
+ @Test
+ public void testShouldNotPauseAuthOnKeyguard() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ assertFalse(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testShouldPauseAuthIsLaunchTransitionFadingAway() {
+ // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ // WHEN isLaunchTransitionFadingAway=true
+ captureKeyguardStateControllerCallback();
+ when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true);
+ mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged();
+
+ // THEN pause auth
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testShouldPauseAuthOnShadeLocked() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+
+ sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
+
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testShouldPauseAuthOnShade() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+
+ // WHEN not on keyguard yet (shade = home)
+ sendStatusBarStateChanged(StatusBarState.SHADE);
+
+ // THEN pause auth
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testShouldPauseAuthAnimatingScreenOffFromShade() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+
+ // WHEN transitioning from home/shade => keyguard + animating screen off
+ mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD);
+ when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true);
+
+ // THEN pause auth
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testDoNotPauseAuthAnimatingScreenOffFromLS() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+
+ // WHEN animating screen off transition from LS => AOD
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true);
+
+ // THEN don't pause auth
+ assertFalse(mController.shouldPauseAuth());
+ }
+
+ @Test
+ public void testOverrideShouldPauseAuthOnShadeLocked() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureAltAuthInterceptor();
+
+ sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
+ assertTrue(mController.shouldPauseAuth());
+
+ mAltAuthInterceptor.showAlternateAuthBouncer(); // force show
+ assertFalse(mController.shouldPauseAuth());
+ assertTrue(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
+
+ mAltAuthInterceptor.hideAlternateAuthBouncer(); // stop force show
+ assertTrue(mController.shouldPauseAuth());
+ assertFalse(mAltAuthInterceptor.isShowingAlternateAuthBouncer());
+ }
+
+ @Test
+ public void testOnDetachedStateReset() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // WHEN view is detached
+ mController.onViewDetached();
+
+ // THEN remove alternate auth interceptor
+ verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
+ }
+
+ @Test
+ public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer isn't showing
+ mAltAuthInterceptor.hideAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view
+ mController.onTouchOutsideView();
+
+ // THEN bouncer / alt auth methods are never called
+ verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+ }
+
+ @Test
+ public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer is showing
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view 200ms later (just within threshold)
+ mSystemClock.advanceTime(200);
+ mController.onTouchOutsideView();
+
+ // THEN bouncer / alt auth methods are never called because not enough time has passed
+ verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+ }
+
+ @Test
+ public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer is showing
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view 205ms later
+ mSystemClock.advanceTime(205);
+ mController.onTouchOutsideView();
+
+ // THEN show the bouncer
+ verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
+ }
+
+ @Test
+ public void testFadeInWithStatusBarExpansion() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureStatusBarExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ reset(mView);
+
+ // WHEN status bar expansion is 0
+ updateStatusBarExpansion(0, true);
+
+ // THEN alpha is 0
+ verify(mView).setUnpausedAlpha(0);
+ }
+
+ @Test
+ public void testShowUdfpsBouncer() {
+ // GIVEN view is attached and status bar expansion is 0
+ mController.onViewAttached();
+ captureStatusBarExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ captureAltAuthInterceptor();
+ updateStatusBarExpansion(0, true);
+ reset(mView);
+ when(mView.getContext()).thenReturn(mResourceContext);
+ when(mResourceContext.getString(anyInt())).thenReturn("test string");
+
+ // WHEN status bar expansion is 0 but udfps bouncer is requested
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // THEN alpha is 255
+ verify(mView).setUnpausedAlpha(255);
+ }
+
+ @Test
+ public void testTransitionToFullShadeProgress() {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached();
+ captureStatusBarExpansionListeners();
+ updateStatusBarExpansion(1f, true);
+ reset(mView);
+ when(mView.getDialogSuggestedAlpha()).thenReturn(1f);
+
+ // WHEN we're transitioning to the full shade
+ float transitionProgress = .6f;
+ mController.setTransitionToFullShadeProgress(transitionProgress);
+
+ // THEN alpha is between 0 and 255
+ verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
+ }
+
+ @Test
+ public void testShowUdfpsBouncer_transitionToFullShadeProgress() {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached();
+ captureStatusBarExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ captureAltAuthInterceptor();
+ updateStatusBarExpansion(1f, true);
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+ reset(mView);
+
+ // WHEN we're transitioning to the full shade
+ mController.setTransitionToFullShadeProgress(1.0f);
+
+ // THEN alpha is 255 (b/c udfps bouncer is requested)
+ verify(mView).setUnpausedAlpha(255);
+ }
+
+ @Test
+ public void testUpdatePanelExpansion_pauseAuth() {
+ // GIVEN view is attached + on the keyguard
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ reset(mView);
+
+ // WHEN panelViewExpansion changes to hide
+ when(mView.getUnpausedAlpha()).thenReturn(0);
+ updateStatusBarExpansion(0f, false);
+
+ // THEN pause auth is updated to PAUSE
+ verify(mView, atLeastOnce()).setPauseAuth(true);
+ }
+
+ @Test
+ public void testUpdatePanelExpansion_unpauseAuth() {
+ // GIVEN view is attached + on the keyguard + panel expansion is 0f
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ reset(mView);
+
+ // WHEN panelViewExpansion changes to expanded
+ when(mView.getUnpausedAlpha()).thenReturn(255);
+ updateStatusBarExpansion(1f, true);
+
+ // THEN pause auth is updated to NOT pause
+ verify(mView, atLeastOnce()).setPauseAuth(false);
+ }
+
private void captureBouncerExpansionCallback() {
verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
new file mode 100644
index 0000000..54f20db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.any
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenEver
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class UdfpsShellTest : SysuiTestCase() {
+
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ // Unit under test
+ private lateinit var udfpsShell: UdfpsShell
+
+ @Mock lateinit var commandRegistry: CommandRegistry
+ @Mock lateinit var udfpsOverlay: UdfpsOverlay
+ @Mock lateinit var udfpsOverlayController: UdfpsOverlayController
+
+ @Captor private lateinit var motionEvent: ArgumentCaptor<MotionEvent>
+
+ private val sensorBounds = Rect()
+
+ @Before
+ fun setup() {
+ whenEver(udfpsOverlayController.sensorBounds).thenReturn(sensorBounds)
+
+ udfpsShell = UdfpsShell(commandRegistry, udfpsOverlay)
+ udfpsShell.udfpsOverlayController = udfpsOverlayController
+ }
+
+ @Test
+ fun testSimFingerDown() {
+ udfpsShell.simFingerDown()
+
+ verify(udfpsOverlayController, times(2)).debugOnTouch(any(), motionEvent.capture())
+
+ assertEquals(motionEvent.allValues[0].action, MotionEvent.ACTION_DOWN) // ACTION_MOVE
+ assertEquals(motionEvent.allValues[1].action, MotionEvent.ACTION_MOVE) // ACTION_MOVE
+ }
+
+ @Test
+ fun testSimFingerUp() {
+ udfpsShell.simFingerUp()
+
+ verify(udfpsOverlayController).debugOnTouch(any(), motionEvent.capture())
+
+ assertEquals(motionEvent.value.action, MotionEvent.ACTION_UP)
+ }
+
+ @Test
+ fun testOnUiReady() {
+ udfpsShell.onUiReady()
+
+ verify(udfpsOverlayController).debugOnUiReady(any(), any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 6bc7308..f8579ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -39,6 +39,8 @@
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -66,6 +68,8 @@
@Mock
private SingleTapClassifier mSingleTapClassfier;
@Mock
+ private LongTapClassifier mLongTapClassifier;
+ @Mock
private DoubleTapClassifier mDoubleTapClassifier;
@Mock
private FalsingClassifier mClassifierA;
@@ -80,6 +84,7 @@
private AccessibilityManager mAccessibilityManager;
private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
private final FalsingClassifier.Result mFalsedResult =
FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
@@ -94,6 +99,7 @@
when(mClassifierB.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mPassedResult);
when(mSingleTapClassfier.isTap(any(List.class), anyDouble())).thenReturn(mPassedResult);
+ when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mPassedResult);
mClassifiers.add(mClassifierA);
@@ -101,9 +107,9 @@
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
- mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
- mHistoryTracker, mKeyguardStateController, mAccessibilityManager,
- false);
+ mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier,
+ mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
+ mAccessibilityManager, false, mFakeFeatureFlags);
ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor =
@@ -113,6 +119,7 @@
gestureCompleteListenerCaptor.capture());
mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
+ mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
}
@Test
@@ -212,7 +219,7 @@
}
@Test
- public void testIsFalseTap_EmptyRecentEvents() {
+ public void testIsFalseSingleTap_EmptyRecentEvents() {
// Ensure we look at prior events if recent events has already been emptied.
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>());
when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList);
@@ -223,7 +230,7 @@
@Test
- public void testIsFalseTap_RobustCheck_NoFaceAuth() {
+ public void testIsFalseSingleTap_RobustCheck_NoFaceAuth() {
when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mFalsedResult);
@@ -233,13 +240,50 @@
}
@Test
- public void testIsFalseTap_RobustCheck_FaceAuth() {
+ public void testIsFalseSingleTap_RobustCheck_FaceAuth() {
when(mSingleTapClassfier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTap(NO_PENALTY)).isFalse();
}
@Test
+ public void testIsFalseLongTap_EmptyRecentEvents() {
+ // Ensure we look at prior events if recent events has already been emptied.
+ when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(new ArrayList<>());
+ when(mFalsingDataProvider.getPriorMotionEvents()).thenReturn(mMotionEventList);
+
+ mBrightLineFalsingManager.isFalseLongTap(0);
+ verify(mLongTapClassifier).isTap(mMotionEventList, 0);
+ }
+
+ @Test
+ public void testIsFalseLongTap_FalseLongTap_NotFlagged() {
+ mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false);
+ when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+ }
+
+ @Test
+ public void testIsFalseLongTap_FalseLongTap() {
+ when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
+ assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue();
+ }
+
+ @Test
+ public void testIsFalseLongTap_RobustCheck_NoFaceAuth() {
+ when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
+ when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(false);
+ assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+ }
+
+ @Test
+ public void testIsFalseLongTap_RobustCheck_FaceAuth() {
+ when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mPassedResult);
+ when(mFalsingDataProvider.isJustUnlockedWithFace()).thenReturn(true);
+ assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
+ }
+
+ @Test
public void testIsFalseDoubleTap() {
when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mPassedResult);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index b811aab..4281ee0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -32,6 +32,8 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -57,6 +59,8 @@
@Mock
private SingleTapClassifier mSingleTapClassifier;
@Mock
+ private LongTapClassifier mLongTapClassifier;
+ @Mock
private DoubleTapClassifier mDoubleTapClassifier;
@Mock
private FalsingClassifier mClassifierA;
@@ -71,6 +75,7 @@
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
private final FalsingClassifier.Result mFalsedResult =
FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
+ private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
@Before
public void setup() {
@@ -78,15 +83,17 @@
when(mClassifierA.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mFalsedResult);
when(mSingleTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
+ when(mLongTapClassifier.isTap(any(List.class), anyDouble())).thenReturn(mFalsedResult);
when(mDoubleTapClassifier.classifyGesture(anyInt(), anyDouble(), anyDouble()))
.thenReturn(mFalsedResult);
mClassifiers.add(mClassifierA);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
when(mKeyguardStateController.isShowing()).thenReturn(true);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider,
- mMetricsLogger, mClassifiers, mSingleTapClassifier, mDoubleTapClassifier,
- mHistoryTracker, mKeyguardStateController, mAccessibilityManager,
- false);
+ mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
+ mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
+ mAccessibilityManager, false, mFakeFeatureFlags);
+ mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
}
@Test
@@ -105,6 +112,13 @@
@Test
+ public void testA11yDisablesLongTap() {
+ assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isTrue();
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+ assertThat(mBrightLineFalsingManager.isFalseLongTap(1)).isFalse();
+ }
+
+ @Test
public void testA11yDisablesDoubleTap() {
assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isTrue();
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c5a7de4..c234178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -92,6 +92,12 @@
@Mock
BouncerCallbackInteractor mBouncerCallbackInteractor;
+ @Mock
+ DreamOverlayAnimationsController mAnimationsController;
+
+ @Mock
+ DreamOverlayStateController mStateController;
+
DreamOverlayContainerViewController mController;
@Before
@@ -115,7 +121,9 @@
MAX_BURN_IN_OFFSET,
BURN_IN_PROTECTION_UPDATE_INTERVAL,
MILLIS_UNTIL_FULL_JITTER,
- mBouncerCallbackInteractor);
+ mBouncerCallbackInteractor,
+ mAnimationsController,
+ mStateController);
}
@Test
@@ -188,4 +196,31 @@
verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction);
verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false);
}
+
+ @Test
+ public void testStartDreamEntryAnimationsOnAttachedNonLowLight() {
+ when(mStateController.isLowLightActive()).thenReturn(false);
+
+ mController.onViewAttached();
+
+ verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
+ verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+ }
+
+ @Test
+ public void testNeverStartDreamEntryAnimationsOnAttachedForLowLight() {
+ when(mStateController.isLowLightActive()).thenReturn(true);
+
+ mController.onViewAttached();
+
+ verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
+ }
+
+ @Test
+ public void testCancelDreamEntryAnimationsOnDetached() {
+ mController.onViewAttached();
+ mController.onViewDetached();
+
+ verify(mAnimationsController).cancelRunningEntryAnimations();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f370be1..f04a37f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -253,6 +253,7 @@
verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
verify(mStateController).setOverlayActive(false);
verify(mStateController).setLowLightActive(false);
+ verify(mStateController).setEntryAnimationsFinished(false);
}
@Test
@@ -273,27 +274,31 @@
@Test
public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
- when(mDreamOverlayContainerView.getParent())
- .thenReturn(mDreamOverlayContainerViewParent)
- .thenReturn(null);
-
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+ // Destroy the service.
+ mService.onDestroy();
+ mMainExecutor.runAllReady();
+
// Inform the overlay service of dream starting.
overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
false /*shouldShowComplication*/);
-
- // Destroy the service.
- mService.onDestroy();
-
- // Run executor tasks.
mMainExecutor.runAllReady();
verify(mWindowManager, never()).addView(any(), any());
}
@Test
+ public void testNeverRemoveDecorViewIfNotAdded() {
+ // Service destroyed before dream started.
+ mService.onDestroy();
+ mMainExecutor.runAllReady();
+
+ verify(mWindowManager, never()).removeView(any());
+ }
+
+ @Test
public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
final IBinder proxy = mService.onBind(new Intent());
final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index d1d32a1..c21c7a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -234,4 +234,20 @@
verify(mCallback, times(1)).onStateChanged();
assertThat(stateController.isLowLightActive()).isTrue();
}
+
+ @Test
+ public void testNotifyEntryAnimationsFinishedChanged() {
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+
+ stateController.addCallback(mCallback);
+ mExecutor.runAllReady();
+ assertThat(stateController.areEntryAnimationsFinished()).isFalse();
+
+ stateController.setEntryAnimationsFinished(true);
+ mExecutor.runAllReady();
+
+ verify(mCallback, times(1)).onStateChanged();
+ assertThat(stateController.areEntryAnimationsFinished()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index aa02178..85c2819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -19,16 +19,20 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
+import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
import android.net.ConnectivityManager;
@@ -55,6 +59,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -69,7 +74,7 @@
"{count, plural, =1 {# notification} other {# notifications}}";
@Mock
- DreamOverlayStatusBarView mView;
+ MockDreamOverlayStatusBarView mView;
@Mock
ConnectivityManager mConnectivityManager;
@Mock
@@ -105,6 +110,9 @@
@Mock
DreamOverlayStateController mDreamOverlayStateController;
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
private final Executor mMainExecutor = Runnable::run;
DreamOverlayStatusBarViewController mController;
@@ -115,6 +123,8 @@
when(mResources.getString(R.string.dream_overlay_status_bar_notification_indicator))
.thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING);
+ doCallRealMethod().when(mView).setVisibility(anyInt());
+ doCallRealMethod().when(mView).getVisibility();
mController = new DreamOverlayStatusBarViewController(
mView,
@@ -454,12 +464,10 @@
public void testStatusBarHiddenWhenSystemStatusBarShown() {
mController.onViewAttached();
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+ updateEntryAnimationsFinished();
+ updateStatusBarWindowState(true);
- verify(mView).setVisibility(View.INVISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
@@ -467,29 +475,43 @@
mController.onViewAttached();
reset(mView);
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+ updateEntryAnimationsFinished();
+ updateStatusBarWindowState(false);
- verify(mView).setVisibility(View.VISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() {
mController.onViewAttached();
+ updateEntryAnimationsFinished();
mController.onViewDetached();
reset(mView);
- final ArgumentCaptor<StatusBarWindowStateListener>
- callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
- verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
- callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+ updateStatusBarWindowState(true);
verify(mView, never()).setVisibility(anyInt());
}
@Test
+ public void testNoChangeToVisibilityBeforeDreamStartedWhenStatusBarHidden() {
+ mController.onViewAttached();
+
+ // Trigger status bar window state change.
+ final StatusBarWindowStateListener listener = updateStatusBarWindowState(false);
+
+ // Verify no visibility change because dream not started.
+ verify(mView, never()).setVisibility(anyInt());
+
+ // Dream entry animations finished.
+ updateEntryAnimationsFinished();
+
+ // Trigger another status bar window state change, and verify visibility change.
+ listener.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
public void testExtraStatusBarItemSetWhenItemsChange() {
mController.onViewAttached();
when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView);
@@ -507,16 +529,75 @@
public void testLowLightHidesStatusBar() {
when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true);
mController.onViewAttached();
+ updateEntryAnimationsFinished();
- verify(mView).setVisibility(View.INVISIBLE);
- reset(mView);
-
- when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
callbackCapture.getValue().onStateChanged();
- verify(mView).setVisibility(View.VISIBLE);
+ assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
+ reset(mView);
+
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ callbackCapture.getValue().onStateChanged();
+
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void testNoChangeToVisibilityBeforeDreamStartedWhenLowLightStateChange() {
+ when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+ mController.onViewAttached();
+
+ // No change to visibility because dream not fully started.
+ verify(mView, never()).setVisibility(anyInt());
+
+ // Dream entry animations finished.
+ updateEntryAnimationsFinished();
+
+ // Trigger state change and verify visibility changed.
+ final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+ callbackCapture.getValue().onStateChanged();
+
+ assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
+ when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
+ final ArgumentCaptor<StatusBarWindowStateListener>
+ callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+ verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
+ final StatusBarWindowStateListener listener = callbackCapture.getValue();
+ listener.onStatusBarWindowStateChanged(show ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN);
+ return listener;
+ }
+
+ private void updateEntryAnimationsFinished() {
+ when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+
+ verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+ final DreamOverlayStateController.Callback callback = mCallbackCaptor.getValue();
+ callback.onStateChanged();
+ }
+
+ private static class MockDreamOverlayStatusBarView extends DreamOverlayStatusBarView {
+ private int mVisibility = View.VISIBLE;
+
+ private MockDreamOverlayStatusBarView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mVisibility = visibility;
+ }
+
+ @Override
+ public int getVisibility() {
+ return mVisibility;
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index 3b9e398..b477592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.dreams.complication;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -29,16 +30,18 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
@SmallTest
@@ -77,9 +80,20 @@
@Mock
ComplicationLayoutParams mComplicationLayoutParams;
+ @Mock
+ DreamOverlayStateController mDreamOverlayStateController;
+
+ @Captor
+ private ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> mObserverCaptor;
+
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
@Complication.Category
static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
+ private ComplicationHostViewController mController;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -91,6 +105,15 @@
when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+
+ mController = new ComplicationHostViewController(
+ mComplicationHostView,
+ mLayoutEngine,
+ mDreamOverlayStateController,
+ mLifecycleOwner,
+ mViewModel);
+
+ mController.init();
}
/**
@@ -98,25 +121,12 @@
*/
@Test
public void testViewModelObservation() {
- final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
- ArgumentCaptor.forClass(Observer.class);
- final ComplicationHostViewController controller = new ComplicationHostViewController(
- mComplicationHostView,
- mLayoutEngine,
- mLifecycleOwner,
- mViewModel);
-
- controller.init();
-
- verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
- observerArgumentCaptor.capture());
-
final Observer<Collection<ComplicationViewModel>> observer =
- observerArgumentCaptor.getValue();
+ captureComplicationViewModelsObserver();
- // Add complication and ensure it is added to the view.
+ // Add a complication and ensure it is added to the view.
final HashSet<ComplicationViewModel> complications = new HashSet<>(
- Arrays.asList(mComplicationViewModel));
+ Collections.singletonList(mComplicationViewModel));
observer.onChanged(complications);
verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
@@ -127,4 +137,48 @@
verify(mLayoutEngine).removeComplication(eq(mComplicationId));
}
+
+ @Test
+ public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Add a complication before entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should be set to invisible.
+ verify(mComplicationView).setVisibility(View.INVISIBLE);
+ }
+
+ @Test
+ public void testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible() {
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Dream entry animations finished.
+ when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+ final DreamOverlayStateController.Callback stateCallback = captureOverlayStateCallback();
+ stateCallback.onStateChanged();
+
+ // Add a complication after entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should not be set to invisible.
+ verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+ }
+
+ private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
+ verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+ mObserverCaptor.capture());
+ return mObserverCaptor.getValue();
+ }
+
+ private DreamOverlayStateController.Callback captureOverlayStateCallback() {
+ verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+ return mCallbackCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 2c3ddd5..b6780a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -105,6 +106,7 @@
private @Mock ScreenOffAnimationController mScreenOffAnimationController;
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
+ private @Mock ShadeController mShadeController;
private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
@@ -307,6 +309,7 @@
mScreenOnCoordinator,
mInteractionJankMonitor,
mDreamOverlayStateController,
+ () -> mShadeController,
mNotificationShadeWindowControllerLazy,
() -> mActivityLaunchAnimator);
mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
deleted file mode 100644
index 640e6dc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.keyguard;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WorkLockActivityTest extends SysuiTestCase {
- private static final @UserIdInt int USER_ID = 270;
- private static final String CALLING_PACKAGE_NAME = "com.android.test";
-
- private @Mock UserManager mUserManager;
- private @Mock PackageManager mPackageManager;
- private @Mock Context mContext;
- private @Mock BroadcastDispatcher mBroadcastDispatcher;
- private @Mock Drawable mDrawable;
- private @Mock Drawable mBadgedDrawable;
-
- private WorkLockActivity mActivity;
-
- private static class WorkLockActivityTestable extends WorkLockActivity {
- WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher,
- UserManager userManager, PackageManager packageManager) {
- super(broadcastDispatcher, userManager, packageManager);
- attachBaseContext(baseContext);
- }
- }
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher, mUserManager,
- mPackageManager);
- }
-
- @Test
- public void testGetBadgedIcon() throws Exception {
- ApplicationInfo info = new ApplicationInfo();
- when(mPackageManager.getApplicationInfoAsUser(eq(CALLING_PACKAGE_NAME), any(),
- eq(USER_ID))).thenReturn(info);
- when(mPackageManager.getApplicationIcon(eq(info))).thenReturn(mDrawable);
- when(mUserManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID)))).thenReturn(
- mBadgedDrawable);
- mActivity.setIntent(new Intent()
- .putExtra(Intent.EXTRA_USER_ID, USER_ID)
- .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME));
-
- assertEquals(mBadgedDrawable, mActivity.getBadgedIcon());
- }
-
- @Test
- public void testUnregisteredFromDispatcher() {
- mActivity.unregisterBroadcastReceiver();
- verify(mBroadcastDispatcher).unregisterReceiver(any());
- verify(mContext, never()).unregisterReceiver(any());
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
new file mode 100644
index 0000000..c7f1dec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.graphics.drawable.Drawable
+import android.os.Looper
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WorkLockActivityTest : SysuiTestCase() {
+ private val context: Context = mock()
+ private val userManager: UserManager = mock()
+ private val packageManager: PackageManager = mock()
+ private val broadcastDispatcher: BroadcastDispatcher = mock()
+ private val drawable: Drawable = mock()
+ private val badgedDrawable: Drawable = mock()
+ private lateinit var activity: WorkLockActivity
+
+ private class WorkLockActivityTestable
+ constructor(
+ baseContext: Context,
+ broadcastDispatcher: BroadcastDispatcher,
+ userManager: UserManager,
+ packageManager: PackageManager,
+ ) : WorkLockActivity(broadcastDispatcher, userManager, packageManager) {
+ init {
+ attachBaseContext(baseContext)
+ }
+ }
+
+ @Before
+ fun setUp() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare()
+ }
+ activity =
+ WorkLockActivityTestable(
+ baseContext = context,
+ broadcastDispatcher = broadcastDispatcher,
+ userManager = userManager,
+ packageManager = packageManager
+ )
+ }
+
+ @Test
+ fun testGetBadgedIcon() {
+ val info = ApplicationInfo()
+ whenever(
+ packageManager.getApplicationInfoAsUser(
+ eq(CALLING_PACKAGE_NAME),
+ any<ApplicationInfoFlags>(),
+ eq(USER_ID)
+ )
+ )
+ .thenReturn(info)
+ whenever(packageManager.getApplicationIcon(ArgumentMatchers.eq(info))).thenReturn(drawable)
+ whenever(userManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID))))
+ .thenReturn(badgedDrawable)
+ activity.intent =
+ Intent()
+ .putExtra(Intent.EXTRA_USER_ID, USER_ID)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME)
+ assertEquals(badgedDrawable, activity.badgedIcon)
+ }
+
+ @Test
+ fun testUnregisteredFromDispatcher() {
+ activity.unregisterBroadcastReceiver()
+ verify(broadcastDispatcher).unregisterReceiver(any())
+ verify(context, never()).unregisterReceiver(nullable())
+ }
+
+ @Test
+ fun onBackPressed_finishActivity() {
+ assertFalse(activity.isFinishing)
+
+ activity.onBackPressed()
+
+ assertFalse(activity.isFinishing)
+ }
+
+ companion object {
+ @UserIdInt private val USER_ID = 270
+ private const val CALLING_PACKAGE_NAME = "com.android.test"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index f18acba..0fb181d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -23,14 +23,11 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.yield
-/**
- * Fake implementation of a quick affordance data source.
- *
- * This class is abstract to force tests to provide extensions of it as the system that references
- * these configs uses each implementation's class type to refer to them.
- */
-abstract class FakeKeyguardQuickAffordanceConfig(
+/** Fake implementation of a quick affordance data source. */
+class FakeKeyguardQuickAffordanceConfig(
override val key: String,
+ override val pickerName: String = key,
+ override val pickerIconResourceId: Int = 0,
) : KeyguardQuickAffordanceConfig {
var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
new file mode 100644
index 0000000..d2422ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardQuickAffordanceSelectionManager()
+ }
+
+ @Test
+ fun setSelections() =
+ runBlocking(IMMEDIATE) {
+ var affordanceIdsBySlotId: Map<String, List<String>>? = null
+ val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ ),
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(affordanceId2),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1, affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(),
+ )
+ assertSelections(
+ affordanceIdsBySlotId,
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(),
+ )
+ )
+
+ job.cancel()
+ }
+
+ private suspend fun assertSelections(
+ observed: Map<String, List<String>>?,
+ expected: Map<String, List<String>>,
+ ) {
+ assertThat(underTest.getSelections()).isEqualTo(expected)
+ assertThat(observed).isEqualTo(expected)
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 61a3f9f..2bd8e9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -50,7 +50,7 @@
MockitoAnnotations.initMocks(this)
whenever(controller.intent).thenReturn(INTENT_1)
- underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
+ underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index c05beef..5178154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -59,6 +59,7 @@
underTest =
QuickAccessWalletKeyguardQuickAffordanceConfig(
+ mock(),
walletController,
activityStarter,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
new file mode 100644
index 0000000..5a7f2bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+ private lateinit var config1: FakeKeyguardQuickAffordanceConfig
+ private lateinit var config2: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
+ config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+ underTest =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(config1, config2),
+ )
+ }
+
+ @Test
+ fun setSelections() =
+ runBlocking(IMMEDIATE) {
+ var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null
+ val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this)
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+
+ underTest.setSelections(slotId1, listOf(config1.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to listOf(config1),
+ ),
+ )
+
+ underTest.setSelections(slotId2, listOf(config2.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to listOf(config1),
+ slotId2 to listOf(config2),
+ ),
+ )
+
+ underTest.setSelections(slotId1, emptyList())
+ underTest.setSelections(slotId2, listOf(config1.key))
+ assertSelections(
+ configsBySlotId,
+ mapOf(
+ slotId1 to emptyList(),
+ slotId2 to listOf(config1),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun getAffordancePickerRepresentations() {
+ assertThat(underTest.getAffordancePickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config1.key,
+ name = config1.pickerName,
+ iconResourceId = config1.pickerIconResourceId,
+ ),
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config2.key,
+ name = config2.pickerName,
+ iconResourceId = config2.pickerIconResourceId,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun getSlotPickerRepresentations() {
+ assertThat(underTest.getSlotPickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ maxSelectedAffordances = 1,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ maxSelectedAffordances = 1,
+ ),
+ )
+ )
+ }
+
+ private suspend fun assertSelections(
+ observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
+ expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
+ ) {
+ assertThat(observed).isEqualTo(expected)
+ assertThat(underTest.getSelections())
+ .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
+ expected.forEach { (slotId, configs) ->
+ assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
+ }
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index b9ab9d3..53d9b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -21,8 +21,10 @@
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -46,6 +48,7 @@
@Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
private lateinit var underTest: KeyguardRepositoryImpl
@@ -59,6 +62,7 @@
keyguardStateController,
dozeHost,
wakefulnessLifecycle,
+ biometricUnlockController,
)
}
@@ -190,7 +194,7 @@
}
@Test
- fun wakefullness() = runBlockingTest {
+ fun wakefulness() = runBlockingTest {
val values = mutableListOf<WakefulnessModel>()
val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
@@ -217,4 +221,63 @@
job.cancel()
verify(wakefulnessLifecycle).removeObserver(captor.value)
}
+
+ @Test
+ fun isBouncerShowing() = runBlockingTest {
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun biometricUnlockState() = runBlockingTest {
+ val values = mutableListOf<BiometricUnlockModel>()
+ val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+ val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+ verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+ captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be NONE, followed by onModeChanged() call
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.WAKE_AND_UNLOCK,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockModel.SHOW_BOUNCER,
+ BiometricUnlockModel.ONLY_WAKE,
+ BiometricUnlockModel.UNLOCK_COLLAPSING,
+ BiometricUnlockModel.DISMISS_BOUNCER,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+ )
+ )
+
+ job.cancel()
+ verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 64913c7c..27d5d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -128,11 +128,15 @@
assertThat(steps.size).isEqualTo(3)
assertThat(steps[0])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
assertThat(steps[1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME)
+ )
assertThat(steps[2])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ .isEqualTo(
+ TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)
+ )
job.cancel()
}
@@ -174,15 +178,22 @@
}
private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
- assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+ assertThat(steps[0])
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
fractions.forEachIndexed { index, fraction ->
assertThat(steps[index + 1])
.isEqualTo(
- TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+ TransitionStep(
+ AOD,
+ LOCKSCREEN,
+ fraction.toFloat(),
+ TransitionState.RUNNING,
+ OWNER_NAME
+ )
)
}
assertThat(steps[steps.size - 1])
- .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+ .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
assertThat(wtfHandler.failed).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
index c4e7dd2..5743b2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -27,8 +27,8 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -57,6 +57,7 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var repository: KeyguardBouncerRepository
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
@@ -86,6 +87,7 @@
)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
`when`(repository.show.value).thenReturn(null)
+ `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
}
@Test
@@ -124,7 +126,6 @@
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setShowingSoon(false)
- verify(repository).setOnDismissAction(null)
verify(repository).setVisible(false)
verify(repository).setHide(true)
verify(repository).setShow(null)
@@ -178,8 +179,7 @@
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
- verify(repository)
- .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
}
@Test
@@ -269,10 +269,9 @@
@Test
fun testWillDismissWithAction() {
- `when`(repository.onDismissAction.value?.onDismissAction)
- .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
- `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 7116cc1..8b6603d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,10 +25,14 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
@@ -37,6 +41,8 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
@@ -189,6 +195,8 @@
/* startActivity= */ true,
),
)
+
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -213,10 +221,20 @@
whenever(expandable.activityLaunchController()).thenReturn(animationController)
homeControls =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+ val quickAccessWallet =
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
+ val qrCodeScanner =
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ )
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -229,14 +247,8 @@
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {},
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {},
+ quickAccessWallet,
+ qrCodeScanner,
),
),
),
@@ -244,6 +256,11 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ },
+ repository = { quickAffordanceRepository },
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index ae32ba6..3364535 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,22 +22,31 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.yield
import org.junit.Before
@@ -47,6 +56,7 @@
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@@ -62,6 +72,7 @@
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
@@ -71,20 +82,25 @@
repository.setKeyguardShowing(true)
homeControls =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
quickAccessWallet =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {}
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
qrCodeScanner =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ )
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ }
underTest =
KeyguardQuickAffordanceInteractor(
@@ -107,6 +123,8 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags = featureFlags,
+ repository = { quickAffordanceRepository },
)
}
@@ -210,6 +228,270 @@
job.cancel()
}
+ @Test
+ fun select() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ var startConfig: KeyguardQuickAffordanceModel? = null
+ val job1 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { startConfig = it }
+ .launchIn(this)
+ var endConfig: KeyguardQuickAffordanceModel? = null
+ val job2 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { endConfig = it }
+ .launchIn(this)
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${homeControls.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(homeControls.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ underTest.select(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ quickAccessWallet.key
+ )
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(quickAccessWallet.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
+ yield()
+ yield()
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+ "::${qrCodeScanner.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(quickAccessWallet.key),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(qrCodeScanner.key),
+ )
+ )
+
+ job1.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun `unselect - one`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ var startConfig: KeyguardQuickAffordanceModel? = null
+ val job1 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ .onEach { startConfig = it }
+ .launchIn(this)
+ var endConfig: KeyguardQuickAffordanceModel? = null
+ val job2 =
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ .onEach { endConfig = it }
+ .launchIn(this)
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+ yield()
+ yield()
+
+ underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Visible(
+ configKey =
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+ "::${quickAccessWallet.key}",
+ icon = ICON,
+ activationState = ActivationState.NotSupported,
+ )
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(quickAccessWallet.key),
+ )
+ )
+
+ underTest.unselect(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ quickAccessWallet.key
+ )
+ yield()
+ yield()
+
+ assertThat(startConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(endConfig)
+ .isEqualTo(
+ KeyguardQuickAffordanceModel.Hidden,
+ )
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+
+ job1.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun `unselect - all`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+ qrCodeScanner.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+ )
+
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+ yield()
+ yield()
+ underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+ yield()
+ yield()
+
+ underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
+ yield()
+ yield()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(quickAccessWallet.key),
+ )
+ )
+
+ underTest.unselect(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ null,
+ )
+ yield()
+ yield()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf<String, List<String>>(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+ )
+ )
+ }
+
companion object {
private val ICON: Icon = mock {
whenever(this.contentDescription)
@@ -220,5 +502,6 @@
)
}
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 0424c28..6333b24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -54,9 +54,11 @@
@Test
fun `transition collectors receives only appropriate events`() =
runBlocking(IMMEDIATE) {
- var goneToAodSteps = mutableListOf<TransitionStep>()
+ var lockscreenToAodSteps = mutableListOf<TransitionStep>()
val job1 =
- underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this)
+ underTest.lockscreenToAodTransition
+ .onEach { lockscreenToAodSteps.add(it) }
+ .launchIn(this)
var aodToLockscreenSteps = mutableListOf<TransitionStep>()
val job2 =
@@ -70,14 +72,14 @@
steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(GONE, AOD, 0f, STARTED))
- steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING))
- steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
steps.forEach { repository.sendTransitionStep(it) }
assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
- assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8))
+ assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
job1.cancel()
job2.cancel()
@@ -119,10 +121,8 @@
fun keyguardStateTests() =
runBlocking(IMMEDIATE) {
var finishedSteps = mutableListOf<KeyguardState>()
- val job1 =
+ val job =
underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
- var startedSteps = mutableListOf<KeyguardState>()
- val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this)
val steps = mutableListOf<TransitionStep>()
@@ -137,10 +137,60 @@
steps.forEach { repository.sendTransitionStep(it) }
assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
- assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE))
- job1.cancel()
- job2.cancel()
+ job.cancel()
+ }
+
+ @Test
+ fun finishedKeyguardTransitionStepTests() =
+ runBlocking(IMMEDIATE) {
+ var finishedSteps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.finishedKeyguardTransitionStep
+ .onEach { finishedSteps.add(it) }
+ .launchIn(this)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach { repository.sendTransitionStep(it) }
+
+ assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
+
+ job.cancel()
+ }
+
+ @Test
+ fun startedKeyguardTransitionStepTests() =
+ runBlocking(IMMEDIATE) {
+ var startedSteps = mutableListOf<TransitionStep>()
+ val job =
+ underTest.startedKeyguardTransitionStep
+ .onEach { startedSteps.add(it) }
+ .launchIn(this)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach { repository.sendTransitionStep(it) }
+
+ assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+
+ job.cancel()
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index f73d1ec..78148c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,10 +23,14 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
@@ -41,6 +45,8 @@
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -82,20 +88,13 @@
.thenReturn(RETURNED_BURN_IN_OFFSET)
homeControlsQuickAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
quickAccessWalletAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- ) {}
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ )
qrCodeScannerAffordanceConfig =
- object :
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
- ) {}
+ FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -116,6 +115,18 @@
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ scope = CoroutineScope(IMMEDIATE),
+ backgroundDispatcher = IMMEDIATE,
+ selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ configs =
+ setOf(
+ homeControlsQuickAffordanceConfig,
+ quickAccessWalletAffordanceConfig,
+ qrCodeScannerAffordanceConfig,
+ ),
+ )
underTest =
KeyguardBottomAreaViewModel(
keyguardInteractor = keyguardInteractor,
@@ -127,6 +138,11 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ },
+ repository = { quickAffordanceRepository },
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
@@ -576,5 +592,6 @@
companion object {
private const val DEFAULT_BURN_IN_OFFSET = 5
private const val RETURNED_BURN_IN_OFFSET = 3
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index c8e8943..6ca34e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -49,6 +49,7 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -119,6 +120,7 @@
MediaPlayerData.clear()
}
+ @Ignore("b/253229241")
@Test
fun testPlayerOrdering() {
// Test values: key, data, last active time
@@ -295,6 +297,7 @@
}
}
+ @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_prioritized() {
testPlayerOrdering()
@@ -312,6 +315,7 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
}
+ @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
testPlayerOrdering()
@@ -328,6 +332,7 @@
assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
}
+ @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_notPrioritized() {
testPlayerOrdering()
@@ -346,6 +351,7 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
}
+ @Ignore("b/253229241")
@Test
fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
testPlayerOrdering()
@@ -382,6 +388,8 @@
MediaPlayerData.playerKeys().elementAt(0)
)
}
+
+ @Ignore("b/253229241")
@Test
fun testSwipeDismiss_logged() {
mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
@@ -389,6 +397,7 @@
verify(logger).logSwipeDismiss()
}
+ @Ignore("b/253229241")
@Test
fun testSettingsButton_logged() {
mediaCarouselController.settingsButton.callOnClick()
@@ -396,6 +405,7 @@
verify(logger).logCarouselSettings()
}
+ @Ignore("b/253229241")
@Test
fun testLocationChangeQs_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -406,6 +416,7 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
}
+ @Ignore("b/253229241")
@Test
fun testLocationChangeQqs_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -416,6 +427,7 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
}
+ @Ignore("b/253229241")
@Test
fun testLocationChangeLockscreen_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -426,6 +438,7 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
}
+ @Ignore("b/253229241")
@Test
fun testLocationChangeDream_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -436,6 +449,7 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
}
+ @Ignore("b/253229241")
@Test
fun testRecommendationRemoved_logged() {
val packageName = "smartspace package"
@@ -449,6 +463,7 @@
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
+ @Ignore("b/253229241")
@Test
fun testMediaLoaded_ScrollToActivePlayer() {
listener.value.onMediaDataLoaded(
@@ -506,6 +521,7 @@
)
}
+ @Ignore("b/253229241")
@Test
fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
listener.value.onSmartspaceMediaDataLoaded(
@@ -549,6 +565,7 @@
assertEquals(playerIndex, 0)
}
+ @Ignore("b/253229241")
@Test
fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
var result = false
@@ -560,6 +577,7 @@
assertEquals(true, result)
}
+ @Ignore("b/253229241")
@Test
fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
var result = false
@@ -573,6 +591,7 @@
assertEquals(true, result)
}
+ @Ignore("b/253229241")
@Test
fun testGetCurrentVisibleMediaContentIntent() {
val clickIntent1 = mock(PendingIntent::class.java)
@@ -619,6 +638,7 @@
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
}
+ @Ignore("b/253229241")
@Test
fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
val delta = 0.0001F
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 6adce7a..c1fa9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -61,6 +61,7 @@
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -201,6 +202,8 @@
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private Resources mResources;
+ @Mock
+ private ViewRootImpl mViewRootImpl;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@@ -227,6 +230,7 @@
when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
.thenReturn(mContext);
when(mNavigationBarView.getResources()).thenReturn(mResources);
+ when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl);
setupSysuiDependency();
// This class inflates views that call Dependency.get, thus these injections are still
// necessary.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index f20c6a2..9758842 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -25,8 +25,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.floating.FloatingTasks
-import java.util.*
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,8 +49,8 @@
@Mock lateinit var context: Context
@Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
- @Mock lateinit var floatingTasks: FloatingTasks
- @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+ @Mock lateinit var bubbles: Bubbles
+ @Mock lateinit var optionalBubbles: Optional<Bubbles>
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
@Mock lateinit var optionalUserManager: Optional<UserManager>
@@ -61,7 +61,7 @@
MockitoAnnotations.initMocks(this)
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
- whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+ whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
whenever(userManager.isUserUnlocked).thenReturn(true)
@@ -71,7 +71,7 @@
return NoteTaskController(
context = context,
intentResolver = noteTaskIntentResolver,
- optionalFloatingTasks = optionalFloatingTasks,
+ optionalBubbles = optionalBubbles,
optionalKeyguardManager = optionalKeyguardManager,
optionalUserManager = optionalUserManager,
isEnabled = isEnabled,
@@ -85,16 +85,16 @@
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context).startActivity(notesIntent)
- verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() {
+ fun handleSystemKey_keyguardIsUnlocked_shouldStartBubbles() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
- verify(floatingTasks).showOrSetStashed(notesIntent)
+ verify(bubbles).showAppBubble(notesIntent)
verify(context, never()).startActivity(notesIntent)
}
@@ -103,17 +103,17 @@
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
verify(context, never()).startActivity(notesIntent)
- verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() {
- whenever(optionalFloatingTasks.orElse(null)).thenReturn(null)
+ fun handleSystemKey_bubblesIsNull_shouldDoNothing() {
+ whenever(optionalBubbles.orElse(null)).thenReturn(null)
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
- verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
@@ -123,7 +123,7 @@
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
- verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
@@ -133,7 +133,7 @@
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
- verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
@@ -143,7 +143,7 @@
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
- verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
@@ -151,7 +151,7 @@
createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
- verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
@@ -161,6 +161,6 @@
createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
verify(context, never()).startActivity(notesIntent)
- verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ verify(bubbles, never()).showAppBubble(notesIntent)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index f344c8d..334089c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -21,8 +21,8 @@
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.floating.FloatingTasks
-import java.util.*
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,20 +43,20 @@
internal class NoteTaskInitializerTest : SysuiTestCase() {
@Mock lateinit var commandQueue: CommandQueue
- @Mock lateinit var floatingTasks: FloatingTasks
- @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+ @Mock lateinit var bubbles: Bubbles
+ @Mock lateinit var optionalBubbles: Optional<Bubbles>
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(optionalFloatingTasks.isPresent).thenReturn(true)
- whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+ whenever(optionalBubbles.isPresent).thenReturn(true)
+ whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
}
private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
return NoteTaskInitializer(
- optionalFloatingTasks = optionalFloatingTasks,
+ optionalBubbles = optionalBubbles,
lazyNoteTaskController = mock(),
commandQueue = commandQueue,
isEnabled = isEnabled,
@@ -78,8 +78,8 @@
}
@Test
- fun initialize_floatingTasksNotPresent_shouldDoNothing() {
- whenever(optionalFloatingTasks.isPresent).thenReturn(false)
+ fun initialize_bubblesNotPresent_shouldDoNothing() {
+ whenever(optionalBubbles.isPresent).thenReturn(false)
createNoteTaskInitializer().initialize()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 2c76be6..b067ee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -136,6 +137,20 @@
assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
}
+ @Test
+ public void testIconNotAnimatedWhenAllowAnimationsFalse() {
+ ImageView iv = new ImageView(mContext);
+ AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class);
+ State s = new State();
+ s.icon = mock(Icon.class);
+ when(s.icon.getDrawable(any())).thenReturn(d);
+ when(s.icon.getInvisibleDrawable(any())).thenReturn(d);
+
+ mIconView.updateIcon(iv, s, false);
+
+ verify(d, never()).start();
+ }
+
private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
return new Drawable.ConstantState() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 4084cf4..3ae8428 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -42,6 +42,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -52,6 +53,7 @@
import java.util.List;
+@Ignore("b/257089187")
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 45b4353..c98c1f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -486,6 +486,7 @@
mSysUiState,
() -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
+ mKeyguardIndicationController,
mNotificationListContainer,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
@@ -499,8 +500,6 @@
() -> {},
mNotificationShelfController);
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
- mNotificationPanelViewController.setKeyguardIndicationController(
- mKeyguardIndicationController);
ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
verify(mView, atLeast(1)).addOnAttachStateChangeListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 09add65..43c6942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -66,6 +66,8 @@
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var shadeLogger: ShadeLogger
private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
private lateinit var underTest: PulsingGestureListener
@@ -81,6 +83,7 @@
centralSurfaces,
ambientDisplayConfiguration,
statusBarStateController,
+ shadeLogger,
tunerService,
dumpManager
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index cf7f8dd..08933fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -33,9 +33,10 @@
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -135,27 +136,29 @@
public void testOnSystemBarAttributesChanged() {
doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
- BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+ BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+ TEST_LETTERBOX_DETAILS);
}
@Test
public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
- BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+ BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+ TEST_LETTERBOX_DETAILS);
}
private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails) {
mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
letterboxDetails);
waitForIdleSync();
verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
- eq(requestedVisibilities), eq(packageName), eq(letterboxDetails));
+ eq(requestedVisibleTypes), eq(packageName), eq(letterboxDetails));
}
@Test
@@ -492,11 +495,12 @@
}
@Test
- public void testSetUdfpsHbmListener() {
- final IUdfpsHbmListener listener = mock(IUdfpsHbmListener.class);
- mCommandQueue.setUdfpsHbmListener(listener);
+ public void testSetUdfpsRefreshRateCallback() {
+ final IUdfpsRefreshRateRequestCallback callback =
+ mock(IUdfpsRefreshRateRequestCallback.class);
+ mCommandQueue.setUdfpsRefreshRateCallback(callback);
waitForIdleSync();
- verify(mCallbacks).setUdfpsHbmListener(eq(listener));
+ verify(mCallbacks).setUdfpsRefreshRateCallback(eq(callback));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index f96c39f..aa1114b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
@@ -86,6 +87,7 @@
private val mRemoteInputManager: NotificationRemoteInputManager = mock()
private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
private val mHeaderController: NodeController = mock()
+ private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
private lateinit var mEntry: NotificationEntry
private lateinit var mGroupSummary: NotificationEntry
@@ -110,6 +112,7 @@
mHeadsUpViewBinder,
mNotificationInterruptStateProvider,
mRemoteInputManager,
+ mLaunchFullScreenIntentProvider,
mHeaderController,
mExecutor)
mCoordinator.attach(mNotifPipeline)
@@ -242,6 +245,20 @@
}
@Test
+ fun testOnEntryAdded_shouldFullScreen() {
+ setShouldFullScreen(mEntry)
+ mCollectionListener.onEntryAdded(mEntry)
+ verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+ }
+
+ @Test
+ fun testOnEntryAdded_shouldNotFullScreen() {
+ setShouldFullScreen(mEntry, should = false)
+ mCollectionListener.onEntryAdded(mEntry)
+ verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+ }
+
+ @Test
fun testPromotesAddedHUN() {
// GIVEN the current entry should heads up
whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
@@ -794,6 +811,11 @@
.thenReturn(should)
}
+ private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) {
+ whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .thenReturn(should)
+ }
+
private fun finishBind(entry: NotificationEntry) {
verify(mHeadsUpManager, never()).showNotification(entry)
withArgCaptor<BindCallback> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 137842e..12cc114 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -232,7 +232,6 @@
@Test
public void testUserLockedResetEvenWhenNoChildren() {
mGroupRow.setUserLocked(true);
- mGroupRow.removeAllChildren();
mGroupRow.setUserLocked(false);
assertFalse("The childrencontainer should not be userlocked but is, the state "
+ "seems out of sync.", mGroupRow.getChildrenContainer().isUserLocked());
@@ -240,12 +239,11 @@
@Test
public void testReinflatedOnDensityChange() {
- mGroupRow.setUserLocked(true);
- mGroupRow.removeAllChildren();
- mGroupRow.setUserLocked(false);
NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
- mGroupRow.setChildrenContainer(mockContainer);
- mGroupRow.onDensityOrFontScaleChanged();
+ mNotifRow.setChildrenContainer(mockContainer);
+
+ mNotifRow.onDensityOrFontScaleChanged();
+
verify(mockContainer).reInflateViews(any(), any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 4dea6be..b850cf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -136,7 +136,7 @@
mAuthController, mStatusBarStateController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
- mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
+ mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index e1f20d5..5ebaf69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -29,7 +29,7 @@
import android.os.PowerManager;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -177,7 +177,7 @@
AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
boolean navbarColorManagedByIme = true;
int behavior = 456;
- InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ int requestedVisibleTypes = WindowInsets.Type.systemBars();
String packageName = "test package name";
LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
@@ -187,7 +187,7 @@
appearanceRegions,
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails);
@@ -197,7 +197,7 @@
appearanceRegions,
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails
);
@@ -209,7 +209,7 @@
AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
boolean navbarColorManagedByIme = true;
int behavior = 456;
- InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ int requestedVisibleTypes = WindowInsets.Type.systemBars();
String packageName = "test package name";
LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
@@ -219,7 +219,7 @@
appearanceRegions,
navbarColorManagedByIme,
behavior,
- requestedVisibilities,
+ requestedVisibleTypes,
packageName,
letterboxDetails);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5755782..7ce3a67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -500,7 +500,7 @@
mKeyguardVieMediatorCallback);
// TODO: we should be able to call mCentralSurfaces.start() and have all the below values
- // initialized automatically.
+ // initialized automatically and make NPVC private.
mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController;
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index fca9771..287ebba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -30,6 +30,7 @@
import android.view.Display;
import android.view.View;
import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
import android.view.WindowManager;
import androidx.lifecycle.Observer;
@@ -107,7 +108,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
assertTrue(mLightsOutNotifController.areLightsOut());
@@ -121,7 +122,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
assertFalse(mLightsOutNotifController.areLightsOut());
@@ -153,7 +154,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
@@ -174,7 +175,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
@@ -195,7 +196,7 @@
null /* appearanceRegions */,
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
- null /* requestedVisibilities */,
+ WindowInsets.Type.defaultVisible(),
null /* packageName */,
null /* letterboxDetails */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 9c56c26..6fb6893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -45,7 +45,7 @@
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -80,7 +80,7 @@
layout,
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- mock(WifiViewModel.class),
+ mock(WifiUiAdapter.class),
mock(MobileUiAdapter.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
@@ -124,14 +124,14 @@
LinearLayout group,
StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- WifiViewModel wifiViewModel,
+ WifiUiAdapter wifiUiAdapter,
MobileUiAdapter mobileUiAdapter,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
location,
statusBarPipelineFlags,
- wifiViewModel,
+ wifiUiAdapter,
mobileUiAdapter,
contextProvider,
darkIconDispatcher);
@@ -172,7 +172,7 @@
super(group,
StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- mock(WifiViewModel.class),
+ mock(WifiUiAdapter.class),
mock(MobileUiAdapter.class),
contextProvider);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0c35659..ec8d711 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -306,17 +306,6 @@
}
@Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
- when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(
- expansionEvent(
- /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
- /* expanded= */ true,
- /* tracking= */ false));
- verify(mBouncer, never()).setExpansion(anyFloat());
- }
-
- @Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(
@@ -361,7 +350,6 @@
@Test
public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
- when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
mStatusBarKeyguardViewManager.show(null);
mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index c409857..ce54d78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -67,8 +67,8 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -123,8 +123,6 @@
@Mock
private ShadeControllerImpl mShadeController;
@Mock
- private NotifPipeline mNotifPipeline;
- @Mock
private NotificationVisibilityProvider mVisibilityProvider;
@Mock
private ActivityIntentHelper mActivityIntentHelper;
@@ -197,7 +195,6 @@
getContext(),
mHandler,
mUiBgExecutor,
- mNotifPipeline,
mVisibilityProvider,
headsUpManager,
mActivityStarter,
@@ -222,7 +219,8 @@
mock(NotificationPresenter.class),
mock(NotificationPanelViewController.class),
mActivityLaunchAnimator,
- notificationAnimationProvider
+ notificationAnimationProvider,
+ mock(LaunchFullScreenIntentProvider.class)
);
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
@@ -384,11 +382,9 @@
NotificationEntry entry = mock(NotificationEntry.class);
when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
when(entry.getSbn()).thenReturn(sbn);
- when(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(eq(entry)))
- .thenReturn(true);
// WHEN
- mNotificationActivityStarter.handleFullScreenIntent(entry);
+ mNotificationActivityStarter.launchFullScreenIntent(entry);
// THEN display should try wake up for the full screen intent
verify(mCentralSurfaces).wakeUpForFullScreenIntent();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
index 9957c2a..4d79a53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -3,12 +3,9 @@
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.view.Display
-import android.view.InsetsVisibilities
+import android.view.WindowInsets
import android.view.WindowInsetsController
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
-import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
-import android.view.WindowInsetsController.Appearance
+import android.view.WindowInsetsController.*
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
@@ -27,8 +24,8 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -88,7 +85,7 @@
changeSysBarAttrs(TEST_APPEARANCE)
verify(statusBarStateController)
- .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), any(), any())
+ .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), anyInt(), any())
}
@Test
@@ -97,7 +94,7 @@
verify(statusBarStateController)
.setSystemBarAttributes(
- eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+ eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
}
@Test
@@ -130,7 +127,7 @@
verify(statusBarStateController)
.setSystemBarAttributes(
- eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+ eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
}
@Test
@@ -197,7 +194,7 @@
appearanceRegions,
/* navbarColorManagedByIme= */ false,
WindowInsetsController.BEHAVIOR_DEFAULT,
- InsetsVisibilities(),
+ WindowInsets.Type.defaultVisible(),
"package name",
letterboxDetails)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index c584109..37457b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -40,6 +39,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -70,7 +70,7 @@
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
- private lateinit var viewModel: WifiViewModel
+ private lateinit var viewModel: LocationBasedWifiViewModel
private lateinit var scope: CoroutineScope
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
@@ -105,23 +105,19 @@
scope,
statusBarPipelineFlags,
wifiConstants,
- )
+ ).home
}
@Test
fun constructAndBind_hasCorrectSlot() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, "slotName", viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel)
assertThat(view.slot).isEqualTo("slotName")
}
@Test
fun getVisibleState_icon_returnsIcon() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_ICON, /* animate= */ false)
@@ -130,9 +126,7 @@
@Test
fun getVisibleState_dot_returnsDot() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_DOT, /* animate= */ false)
@@ -141,9 +135,7 @@
@Test
fun getVisibleState_hidden_returnsHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
@@ -155,9 +147,7 @@
@Test
fun setVisibleState_icon_iconShownDotHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_ICON, /* animate= */ false)
@@ -172,9 +162,7 @@
@Test
fun setVisibleState_dot_iconHiddenDotShown() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_DOT, /* animate= */ false)
@@ -189,9 +177,7 @@
@Test
fun setVisibleState_hidden_iconAndDotHidden() {
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
@@ -211,9 +197,7 @@
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -230,9 +214,7 @@
WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
)
- val view = ModernStatusBarWifiView.constructAndBind(
- context, SLOT_NAME, viewModel, StatusBarLocation.HOME
- )
+ val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 4f1fb02..26df03f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +26,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.net.TetheringManager;
@@ -36,6 +38,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
@@ -96,6 +99,9 @@
}).when(mWifiManager).registerSoftApCallback(any(Executor.class),
any(WifiManager.SoftApCallback.class));
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_show_wifi_tethering, true);
+
Handler handler = new Handler(mLooper.getLooper());
mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
@@ -176,4 +182,18 @@
verify(mCallback1).onHotspotAvailabilityChanged(false);
}
+
+ @Test
+ public void testHotspotSupported_resource_false() {
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_show_wifi_tethering, false);
+
+ Handler handler = new Handler(mLooper.getLooper());
+
+ HotspotController controller =
+ new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+
+ verifyNoMoreInteractions(mTetheringManager);
+ assertFalse(controller.isHotspotSupported());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index e496521..fb781e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -23,6 +23,8 @@
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.user.data.repository.FakeUserRepository
@@ -55,6 +57,8 @@
@Mock private lateinit var dismissDialog: () -> Unit
@Mock private lateinit var selectUser: (Int) -> Unit
@Mock private lateinit var switchUser: (Int) -> Unit
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
private lateinit var underTest: GuestUserInteractor
@@ -87,10 +91,18 @@
repository = repository,
),
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
}
@Test
+ fun `registers broadcast receivers`() {
+ verify(resumeSessionReceiver).register()
+ verify(resetOrExitSessionReceiver).register()
+ }
+
+ @Test
fun `onDeviceBootCompleted - allowed to add - create guest`() =
runBlocking(IMMEDIATE) {
setAllowedToAdd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 1680c36c..58f5531 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -21,6 +21,8 @@
import android.app.admin.DevicePolicyManager
import android.os.UserManager
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -48,6 +50,8 @@
@Mock protected lateinit var devicePolicyManager: DevicePolicyManager
@Mock protected lateinit var uiEventLogger: UiEventLogger
@Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
protected lateinit var underTest: UserInteractor
@@ -107,6 +111,8 @@
devicePolicyManager = devicePolicyManager,
refreshUsersScheduler = refreshUsersScheduler,
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c12a868..116023a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,8 @@
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Text
import com.android.systemui.flags.FakeFeatureFlags
@@ -69,6 +71,8 @@
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
private lateinit var underTest: UserSwitcherViewModel
@@ -104,6 +108,8 @@
devicePolicyManager = devicePolicyManager,
refreshUsersScheduler = refreshUsersScheduler,
uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index fa7ebf6a..bee882d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -86,6 +86,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -394,6 +395,7 @@
mCommonNotifCollection,
mNotifPipeline,
mSysUiState,
+ mock(FeatureFlags.class),
syncExecutor);
mBubblesManager.addNotifCallback(mNotifCallback);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
index 34c83bd..d47e88f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -39,6 +39,7 @@
private boolean mShouldEnforceBouncer;
private boolean mIsReportingEnabled;
private boolean mIsFalseRobustTap;
+ private boolean mIsFalseLongTap;
private boolean mDestroyed;
private boolean mIsProximityNear;
@@ -87,6 +88,10 @@
mIsProximityNear = proxNear;
}
+ public void setFalseLongTap(boolean falseLongTap) {
+ mIsFalseLongTap = falseLongTap;
+ }
+
@Override
public boolean isSimpleTap() {
checkDestroyed();
@@ -100,6 +105,12 @@
}
@Override
+ public boolean isFalseLongTap(int penalty) {
+ checkDestroyed();
+ return mIsFalseLongTap;
+ }
+
+ @Override
public boolean isFalseDoubleTap() {
checkDestroyed();
return mIsFalseDoubleTap;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 11178db..627bd09 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import kotlinx.coroutines.flow.Flow
@@ -52,6 +53,12 @@
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
+ private val _isBouncerShowing = MutableStateFlow(false)
+ override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+
+ private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+ override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+
override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 23c7a61..2d6d29a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -62,7 +62,11 @@
}
@Override
- public void setSignalIcon(String slot, WifiIconState state) {
+ public void setWifiIcon(String slot, WifiIconState state) {
+ }
+
+ @Override
+ public void setNewWifiIcon() {
}
@Override
diff --git a/services/art-profile b/services/art-profile
index 3e05078..b6398c0 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -41551,7 +41551,7 @@
HPLcom/android/server/statusbar/StatusBarManagerService$1;->setNavigationBarLumaSamplingEnabled(IZ)V
HSPLcom/android/server/statusbar/StatusBarManagerService$1;->setNotificationDelegate(Lcom/android/server/notification/NotificationDelegate;)V
HPLcom/android/server/statusbar/StatusBarManagerService$1;->setTopAppHidesStatusBar(Z)V+]Lcom/android/internal/statusbar/IStatusBar;Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy;
-PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V
+PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
HPLcom/android/server/statusbar/StatusBarManagerService$1;->setWindowState(III)V
PLcom/android/server/statusbar/StatusBarManagerService$1;->showChargingAnimation(I)V
PLcom/android/server/statusbar/StatusBarManagerService$1;->showRecentApps(Z)V
@@ -41677,6 +41677,11 @@
PLcom/android/server/statusbar/StatusBarManagerService;->setIcon(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V
HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V
HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V
+PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
+PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
+HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V+]Landroid/util/ArrayMap;Landroid/util/ArrayMap;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService;
+HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V+]Landroid/os/Handler;Landroid/os/Handler;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService;
+PLcom/android/server/statusbar/StatusBarManagerService;->setNavBarMode(I)V
PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V
PLcom/android/server/statusbar/StatusBarManagerService;->showAuthenticationDialog(Landroid/hardware/biometrics/PromptInfo;Landroid/hardware/biometrics/IBiometricSysuiReceiver;[IZZIJLjava/lang/String;JI)V
PLcom/android/server/statusbar/StatusBarManagerService;->suppressAmbientDisplay(Z)V
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index fa52ac9..8055afc 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -26,9 +26,15 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.InstallSourceInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
@@ -39,13 +45,19 @@
import android.os.SystemProperties;
import android.util.PackageUtils;
import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.apk.ApkSigningBlockUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.util.FrameworkStatsLog;
+import libcore.util.HexEncoding;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -151,6 +163,117 @@
return 0;
}
+ private void printPackageMeasurements(PackageInfo packageInfo,
+ final PrintWriter pw) {
+ pw.print(mBinaryHashes.get(packageInfo.packageName) + ",");
+ // TODO: To be moved to somewhere more suitable
+ final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
+ ApkSignatureVerifier.verifySignaturesInternal(input,
+ packageInfo.applicationInfo.sourceDir,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, false);
+ if (parseResult.isError()) {
+ pw.println("ERROR: Failed to compute package content digest for "
+ + packageInfo.applicationInfo.sourceDir + "due to: "
+ + parseResult.getErrorMessage());
+ } else {
+ ApkSignatureVerifier.SigningDetailsWithDigests signingDetails =
+ parseResult.getResult();
+ Map<Integer, byte[]> contentDigests = signingDetails.contentDigests;
+ for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+ Integer algorithmId = entry.getKey();
+ byte[] cDigest = entry.getValue();
+ //pw.print("Content digest algorithm: ");
+ switch (algorithmId) {
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+ pw.print("CHUNKED_SHA256:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+ pw.print("CHUNKED_SHA512:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ pw.print("VERITY_CHUNKED_SHA256:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+ pw.print("SHA256:");
+ break;
+ default:
+ pw.print("UNKNOWN:");
+ }
+ pw.print(HexEncoding.encodeToString(cDigest, false));
+ }
+ }
+ }
+
+ private void printPackageInstallationInfo(PackageInfo packageInfo,
+ final PrintWriter pw) {
+ pw.println("--- Package Installation Info ---");
+ pw.println("Current install location: "
+ + packageInfo.applicationInfo.sourceDir);
+ if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
+ String origPackageFilepath = getOriginalPreinstalledLocation(
+ packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
+ pw.println("|--> Package pre-installed location: " + origPackageFilepath);
+ String digest = mBinaryHashes.get(origPackageFilepath);
+ if (digest == null) {
+ pw.println("ERROR finding SHA256-digest from cache...");
+ } else {
+ pw.println("|--> Pre-installed package SHA256-digest: " + digest);
+ }
+ }
+ pw.println("First install time (ms): " + packageInfo.firstInstallTime);
+ pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
+ boolean isPreloaded = (packageInfo.firstInstallTime
+ == packageInfo.lastUpdateTime);
+ pw.println("Is preloaded: " + isPreloaded);
+
+ InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+ packageInfo.packageName);
+ if (installSourceInfo == null) {
+ pw.println("ERROR: Unable to obtain installSourceInfo of "
+ + packageInfo.packageName);
+ } else {
+ pw.println("Installation initiated by: "
+ + installSourceInfo.getInitiatingPackageName());
+ pw.println("Installation done by: "
+ + installSourceInfo.getInstallingPackageName());
+ pw.println("Installation originating from: "
+ + installSourceInfo.getOriginatingPackageName());
+ }
+
+ if (packageInfo.isApex) {
+ pw.println("Is an active APEX: " + packageInfo.isActiveApex);
+ }
+ }
+
+ private void printPackageSignerDetails(SigningInfo signerInfo,
+ final PrintWriter pw) {
+ if (signerInfo == null) {
+ pw.println("ERROR: Package's signingInfo is null.");
+ return;
+ }
+ pw.println("--- Package Signer Info ---");
+ pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
+ Signature[] packageSigners = signerInfo.getApkContentsSigners();
+ for (Signature packageSigner : packageSigners) {
+ byte[] packageSignerDigestBytes =
+ PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
+ String packageSignerDigestHextring =
+ HexEncoding.encodeToString(packageSignerDigestBytes, false);
+ pw.println("Signer cert's SHA256-digest: " + packageSignerDigestHextring);
+ try {
+ PublicKey publicKey = packageSigner.getPublicKey();
+ pw.println("Signing key algorithm: " + publicKey.getAlgorithm());
+ } catch (CertificateException e) {
+ Slog.e(TAG,
+ "Failed to obtain public key of signer for cert with hash: "
+ + packageSignerDigestHextring);
+ e.printStackTrace();
+ }
+ }
+
+ }
+
private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
pw.println("--- Module Details ---");
pw.println("Module name: " + moduleInfo.getName());
@@ -190,28 +313,35 @@
return -1;
}
- pw.println("APEX Info:");
+ if (!verbose) {
+ pw.println("APEX Info [Format: package_name,package_version,"
+ + "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
for (PackageInfo packageInfo : getInstalledApexs()) {
+ if (verbose) {
+ pw.println("APEX Info [Format: package_name,package_version,"
+ + "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
String packageName = packageInfo.packageName;
- pw.println(packageName + ";"
- + packageInfo.getLongVersionCode() + ":"
- + mBinaryHashes.get(packageName).toLowerCase());
+ pw.print(packageName + ","
+ + packageInfo.getLongVersionCode() + ",");
+ printPackageMeasurements(packageInfo, pw);
+ pw.print("\n");
if (verbose) {
- pw.println("Install location: "
- + packageInfo.applicationInfo.sourceDir);
- pw.println("Last Update Time (ms): " + packageInfo.lastUpdateTime);
-
ModuleInfo moduleInfo;
try {
moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0);
+ pw.println("Is a module: true");
+ printModuleDetails(moduleInfo, pw);
} catch (PackageManager.NameNotFoundException e) {
- pw.println("Is A Module: False");
- pw.println("");
- continue;
+ pw.println("Is a module: false");
}
- pw.println("Is A Module: True");
- printModuleDetails(moduleInfo, pw);
+
+ printPackageInstallationInfo(packageInfo, pw);
+ printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
}
@@ -250,25 +380,37 @@
return -1;
}
- pw.println("Module Info:");
+ if (!verbose) {
+ pw.println("Module Info [Format: package_name,package_version,"
+ + "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
+ if (verbose) {
+ pw.println("Module Info [Format: package_name,package_version,"
+ + "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName,
- PackageManager.MATCH_APEX);
- pw.println(packageInfo.packageName + ";"
- + packageInfo.getLongVersionCode() + ":"
- + mBinaryHashes.get(packageName).toLowerCase());
+ PackageManager.MATCH_APEX
+ | PackageManager.GET_SIGNING_CERTIFICATES);
+ //pw.print("package:");
+ pw.print(packageInfo.packageName + ",");
+ pw.print(packageInfo.getLongVersionCode() + ",");
+ printPackageMeasurements(packageInfo, pw);
+ pw.print("\n");
if (verbose) {
- pw.println("Install location: "
- + packageInfo.applicationInfo.sourceDir);
printModuleDetails(module, pw);
+ printPackageInstallationInfo(packageInfo, pw);
+ printPackageSignerDetails(packageInfo.signingInfo, pw);
pw.println("");
}
} catch (PackageManager.NameNotFoundException e) {
pw.println(packageName
- + ";ERROR:Unable to find PackageInfo for this module.");
+ + ",ERROR:Unable to find PackageInfo for this module.");
if (verbose) {
printModuleDetails(module, pw);
pw.println("");
@@ -468,7 +610,8 @@
return results;
}
List<PackageInfo> allPackages = pm.getInstalledPackages(
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX
+ | PackageManager.GET_SIGNING_CERTIFICATES));
if (allPackages == null) {
Slog.e(TAG, "Error obtaining installed packages (including APEX)");
return results;
@@ -478,6 +621,21 @@
return results;
}
+ @Nullable
+ private InstallSourceInfo getInstallSourceInfo(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ Slog.e(TAG, "Error obtaining an instance of PackageManager.");
+ return null;
+ }
+ try {
+ return pm.getInstallSourceInfo(packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
/**
* Updates the internal data structure with the most current APEX measurements.
@@ -539,6 +697,14 @@
return true;
}
+ private String getOriginalPreinstalledLocation(String packageName,
+ String currentInstalledLocation) {
+ if (currentInstalledLocation.contains("/decompressed/")) {
+ return "/system/apex/" + packageName + ".capex";
+ }
+ return "/system/apex" + packageName + "apex";
+ }
+
private void doFreshBinaryMeasurements() {
PackageManager pm = mContext.getPackageManager();
Slog.d(TAG, "Obtained package manager");
@@ -567,6 +733,27 @@
mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
}
+ for (PackageInfo packageInfo : getInstalledApexs()) {
+ ApplicationInfo appInfo = packageInfo.applicationInfo;
+ if (!appInfo.sourceDir.startsWith("/data/")) {
+ continue;
+ }
+ String origInstallPath = getOriginalPreinstalledLocation(packageInfo.packageName,
+ appInfo.sourceDir);
+
+ // compute SHA256 for the orig /system APEXs
+ String sha256digest = PackageUtils.computeSha256DigestForLargeFile(origInstallPath,
+ largeFileBuffer);
+ if (sha256digest == null) {
+ Slog.e(TAG, String.format("Failed to compute SHA256 digest for file at %s",
+ origInstallPath));
+ mBinaryHashes.put(origInstallPath, BINARY_HASH_ERROR);
+ } else {
+ mBinaryHashes.put(origInstallPath, sha256digest);
+ }
+ // there'd be no entry into mBinaryLastUpdateTimes
+ }
+
// Next, get all installed modules from PackageManager - skip over those APEXs we've
// processed above
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c1c9fbb..b56d1fc 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -231,7 +231,12 @@
String namespaceToReset = namespaceIt.next();
Properties properties = new Properties.Builder(namespaceToReset).build();
try {
- DeviceConfig.setProperties(properties);
+ if (!DeviceConfig.setProperties(properties)) {
+ logCriticalInfo(Log.ERROR, "Failed to clear properties under "
+ + namespaceToReset
+ + ". Running `device_config get_sync_disabled_for_tests` will confirm"
+ + " if config-bulk-update is enabled.");
+ }
} catch (DeviceConfig.BadConfigException exception) {
logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
+ " is already banned, skip reset.");
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index 9323d95..7f24c52 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@@ -192,10 +193,12 @@
private static void dumpStackTraces() {
final ArrayList<Integer> pids = new ArrayList<>();
pids.add(Process.myPid());
- ActivityManagerService.dumpStackTraces(pids, /* processCpuTracker= */null,
- /* lastPids= */null, Watchdog.getInterestingNativePids(),
+ ActivityManagerService.dumpStackTraces(pids,
+ /* processCpuTracker= */null, /* lastPids= */null,
+ CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()),
/* logExceptionCreatingFile= */null, /* subject= */null,
- /* criticalEventSection= */null, /* latencyTracker= */null);
+ /* criticalEventSection= */null, Runnable::run,
+ /* latencyTracker= */null);
}
@Override
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 83d86cd..953e850 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -371,6 +371,7 @@
// 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
// called onUserSwitching(), so calling it before onUserStarting() make it more
// consistent with that
+ EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
}
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
@@ -381,14 +382,30 @@
*
* <p><b>NOTE: </b>this method should only be called when a user that is already running become
* visible; if the user is starting visible, callers should call
- * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead
+ * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead.
*/
public void onUserVisible(@UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId);
+ EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
onUser(USER_VISIBLE, userId);
}
/**
+ * Updates the visibility of the system user.
+ *
+ * <p>Since the system user never stops, this method must be called when it's switched from / to
+ * foreground.
+ */
+ public void onSystemUserVisibilityChanged(boolean visible) {
+ int userId = UserHandle.USER_SYSTEM;
+ EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
+ if (visible) {
+ onUser(USER_VISIBLE, userId);
+ } else {
+ onUser(USER_INVISIBLE, userId);
+ }
+ }
+
+ /**
* Unlocks the given user.
*/
public void onUserUnlocking(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b00dec0..6eeb906 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -49,7 +49,7 @@
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.ProcessCpuTracker;
@@ -75,6 +75,7 @@
import java.util.List;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -894,8 +895,9 @@
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
StringWriter tracesFileException = new StringWriter();
final File stack = ActivityManagerService.dumpStackTraces(
- pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids(),
- tracesFileException, subject, criticalEvents, /* latencyTracker= */null);
+ pids, processCpuTracker, new SparseBooleanArray(),
+ CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException,
+ subject, criticalEvents, Runnable::run, /* latencyTracker= */null);
// Give some extra time to make sure the stack traces get written.
// The system's been hanging for a whlie, another second or two won't hurt much.
SystemClock.sleep(5000);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 062afe9..a17b5e2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -332,6 +332,7 @@
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -467,11 +468,15 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
+import java.util.function.Supplier;
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -3427,14 +3432,15 @@
* @param lastPids of dalvik VM processes to dump stack traces for last
* @param nativePids optional list of native pids to dump stack crawls
* @param logExceptionCreatingFile optional writer to which we log errors creating the file
+ * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
* @param latencyTracker the latency tracker instance of the current ANR.
*/
public static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
- ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
- AnrLatencyTracker latencyTracker) {
- return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
- logExceptionCreatingFile, null, null, null, latencyTracker);
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+ @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+ return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+ logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
}
/**
@@ -3445,14 +3451,17 @@
* @param logExceptionCreatingFile optional writer to which we log errors creating the file
* @param subject optional line related to the error
* @param criticalEventSection optional lines containing recent critical events.
+ * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
* @param latencyTracker the latency tracker instance of the current ANR.
*/
public static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
- ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
- String subject, String criticalEventSection, AnrLatencyTracker latencyTracker) {
- return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
- logExceptionCreatingFile, null, subject, criticalEventSection, latencyTracker);
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+ String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
+ AnrLatencyTracker latencyTracker) {
+ return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+ logExceptionCreatingFile, null, subject, criticalEventSection,
+ auxiliaryTaskExecutor, latencyTracker);
}
/**
@@ -3460,49 +3469,26 @@
* of the very first pid to be dumped.
*/
/* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
- ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
- AnrLatencyTracker latencyTracker) {
+ @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
try {
+
if (latencyTracker != null) {
latencyTracker.dumpStackTracesStarted();
}
- ArrayList<Integer> extraPids = null;
- Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);
+ Slog.i(TAG, "dumpStackTraces pids=" + lastPids);
// Measure CPU usage as soon as we're called in order to get a realistic sampling
// of the top users at the time of the request.
- if (processCpuTracker != null) {
- if (latencyTracker != null) {
- latencyTracker.processCpuTrackerMethodsCalled();
- }
- processCpuTracker.init();
- try {
- Thread.sleep(200);
- } catch (InterruptedException ignored) {
- }
-
- processCpuTracker.update();
-
- // We'll take the stack crawls of just the top apps using CPU.
- final int workingStatsNumber = processCpuTracker.countWorkingStats();
- extraPids = new ArrayList<>();
- for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
- ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
- if (lastPids.indexOfKey(stats.pid) >= 0) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
-
- extraPids.add(stats.pid);
- } else {
- Slog.i(TAG, "Skipping next CPU consuming process, not a java proc: "
- + stats.pid);
- }
- }
- if (latencyTracker != null) {
- latencyTracker.processCpuTrackerMethodsReturned();
- }
+ Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
+ ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
+ Future<ArrayList<Integer>> extraPidsFuture = null;
+ if (extraPidsSupplier != null) {
+ extraPidsFuture =
+ CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
}
final File tracesDir = new File(ANR_TRACE_DIR);
@@ -3536,7 +3522,8 @@
}
long firstPidEndPos = dumpStackTraces(
- tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids, latencyTracker);
+ tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
+ extraPidsFuture, latencyTracker);
if (firstPidEndOffset != null) {
firstPidEndOffset.set(firstPidEndPos);
}
@@ -3554,6 +3541,42 @@
private static SimpleDateFormat sAnrFileDateFormat;
static final String ANR_FILE_PREFIX = "anr_";
+ private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
+ SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
+ if (latencyTracker != null) {
+ latencyTracker.processCpuTrackerMethodsCalled();
+ }
+ ArrayList<Integer> extraPids = new ArrayList<>();
+ processCpuTracker.init();
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignored) {
+ }
+
+ processCpuTracker.update();
+
+ // We'll take the stack crawls of just the top apps using CPU.
+ final int workingStatsNumber = processCpuTracker.countWorkingStats();
+ for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
+ ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+ if (lastPids.indexOfKey(stats.pid) >= 0) {
+ if (DEBUG_ANR) {
+ Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+ }
+
+ extraPids.add(stats.pid);
+ } else {
+ Slog.i(TAG,
+ "Skipping next CPU consuming process, not a java proc: "
+ + stats.pid);
+ }
+ }
+ if (latencyTracker != null) {
+ latencyTracker.processCpuTrackerMethodsReturned();
+ }
+ return extraPids;
+ }
+
private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
if (sAnrFileDateFormat == null) {
sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
@@ -3660,8 +3683,8 @@
* @return The end offset of the trace of the very first PID
*/
public static long dumpStackTraces(String tracesFile,
- ArrayList<Integer> firstPids, ArrayList<Integer> nativePids,
- ArrayList<Integer> extraPids, AnrLatencyTracker latencyTracker) {
+ ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
+ Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) {
Slog.i(TAG, "Dumping to " + tracesFile);
@@ -3682,6 +3705,7 @@
if (latencyTracker != null) {
latencyTracker.dumpingFirstPidsStarted();
}
+
int num = firstPids.size();
for (int i = 0; i < num; i++) {
final int pid = firstPids.get(i);
@@ -3723,6 +3747,10 @@
}
// Next collect the stacks of the native pids
+ ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");
+
+ Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);
+
if (nativePids != null) {
if (latencyTracker != null) {
latencyTracker.dumpingNativePidsStarted();
@@ -3758,6 +3786,19 @@
}
// Lastly, dump stacks for all extra PIDs from the CPU tracker.
+ ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");
+
+ if (extraPidsFuture != null) {
+ try {
+ extraPids = extraPidsFuture.get();
+ } catch (ExecutionException e) {
+ Slog.w(TAG, "Failed to collect extra pids", e.getCause());
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while collecting extra pids", e);
+ }
+ }
+ Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);
+
if (extraPids != null) {
if (latencyTracker != null) {
latencyTracker.dumpingExtraPidsStarted();
@@ -3793,6 +3834,24 @@
return firstPidEnd;
}
+ private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
+ String logName) {
+
+ ArrayList<Integer> pids = null;
+
+ if (pidsFuture == null) {
+ return pids;
+ }
+ try {
+ pids = pidsFuture.get();
+ } catch (ExecutionException e) {
+ Slog.w(TAG, "Failed to collect " + logName, e.getCause());
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while collecting " + logName , e);
+ }
+ return pids;
+ }
+
@Override
public boolean clearApplicationUserData(final String packageName, boolean keepState,
final IPackageDataObserver observer, int userId) {
@@ -14664,6 +14723,15 @@
mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
}
+ final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+ final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+ final String callerPackage = info != null ? info.packageName : original.callerPackage;
+ if (callerPackage != null) {
+ mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+ original.callingUid, 0, callerPackage).sendToTarget();
+ }
+ }
+
final Intent verifyBroadcastLocked(Intent intent) {
// Refuse possible leaked file descriptors
if (intent != null && intent.hasFileDescriptors() == true) {
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 6de4118..71c80ea 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -25,10 +25,15 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.TimeoutRecord;
import com.android.server.wm.WindowProcessController;
import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -51,6 +56,14 @@
*/
private static final long CONSECUTIVE_ANR_TIME_MS = TimeUnit.MINUTES.toMillis(2);
+ /**
+ * The keep alive time for the threads in the helper threadpool executor
+ */
+ private static final int AUX_THREAD_KEEP_ALIVE_SECOND = 10;
+
+ private static final ThreadFactory sDefaultThreadFactory = r ->
+ new Thread(r, "AnrAuxiliaryTaskExecutor");
+
@GuardedBy("mAnrRecords")
private final ArrayList<AnrRecord> mAnrRecords = new ArrayList<>();
private final AtomicBoolean mRunning = new AtomicBoolean(false);
@@ -66,8 +79,18 @@
@GuardedBy("mAnrRecords")
private int mProcessingPid = -1;
+ private final ExecutorService mAuxiliaryTaskExecutor;
+
AnrHelper(final ActivityManagerService service) {
+ this(service, new ThreadPoolExecutor(/* corePoolSize= */ 0, /* maximumPoolSize= */ 1,
+ /* keepAliveTime= */ AUX_THREAD_KEEP_ALIVE_SECOND, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>(), sDefaultThreadFactory));
+ }
+
+ @VisibleForTesting
+ AnrHelper(ActivityManagerService service, ExecutorService auxExecutor) {
mService = service;
+ mAuxiliaryTaskExecutor = auxExecutor;
}
void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) {
@@ -108,7 +131,8 @@
}
timeoutRecord.mLatencyTracker.anrRecordPlacingOnQueueWithSize(mAnrRecords.size());
mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
- parentShortComponentName, parentProcess, aboveSystem, timeoutRecord));
+ parentShortComponentName, parentProcess, aboveSystem,
+ mAuxiliaryTaskExecutor, timeoutRecord));
}
startAnrConsumerIfNeeded();
} finally {
@@ -204,11 +228,12 @@
final ApplicationInfo mAppInfo;
final WindowProcessController mParentProcess;
final boolean mAboveSystem;
+ final ExecutorService mAuxiliaryTaskExecutor;
final long mTimestamp = SystemClock.uptimeMillis();
AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
ApplicationInfo aInfo, String parentShortComponentName,
WindowProcessController parentProcess, boolean aboveSystem,
- TimeoutRecord timeoutRecord) {
+ ExecutorService auxiliaryTaskExecutor, TimeoutRecord timeoutRecord) {
mApp = anrProcess;
mPid = anrProcess.mPid;
mActivityShortComponentName = activityShortComponentName;
@@ -217,6 +242,7 @@
mAppInfo = aInfo;
mParentProcess = parentProcess;
mAboveSystem = aboveSystem;
+ mAuxiliaryTaskExecutor = auxiliaryTaskExecutor;
}
void appNotResponding(boolean onlyDumpSelf) {
@@ -224,7 +250,7 @@
mTimeoutRecord.mLatencyTracker.anrProcessingStarted();
mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
mParentShortComponentName, mParentProcess, mAboveSystem,
- mTimeoutRecord, onlyDumpSelf);
+ mTimeoutRecord, mAuxiliaryTaskExecutor, onlyDumpSelf);
} finally {
mTimeoutRecord.mLatencyTracker.anrProcessingEnded();
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 75d1f68..d1bcf87 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -731,6 +731,8 @@
@Override
@EnforcePermission(BATTERY_STATS)
public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
+ super.getBatteryUsageStats_enforcePermission();
+
awaitCompletion();
if (mBatteryUsageStatsProvider.shouldUpdateStats(queries,
@@ -846,6 +848,8 @@
@Override
@EnforcePermission(BATTERY_STATS)
public long computeBatteryScreenOffRealtimeMs() {
+ super.computeBatteryScreenOffRealtimeMs_enforcePermission();
+
synchronized (mStats) {
final long curTimeUs = SystemClock.elapsedRealtimeNanos() / 1000;
long timeUs = mStats.computeBatteryScreenOffRealtime(curTimeUs,
@@ -857,6 +861,8 @@
@Override
@EnforcePermission(BATTERY_STATS)
public long getScreenOffDischargeMah() {
+ super.getScreenOffDischargeMah_enforcePermission();
+
synchronized (mStats) {
long dischargeUah = mStats.getUahDischargeScreenOff(BatteryStats.STATS_SINCE_CHARGED);
return dischargeUah / 1000;
@@ -866,6 +872,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteEvent(final int code, final String name, final int uid) {
+ super.noteEvent_enforcePermission();
+
if (name == null) {
// TODO(b/194733136): Replace with an IllegalArgumentException throw.
Slog.wtfStack(TAG, "noteEvent called with null name. code = " + code);
@@ -886,6 +894,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteSyncStart(final String name, final int uid) {
+ super.noteSyncStart_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -902,6 +912,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteSyncFinish(final String name, final int uid) {
+ super.noteSyncFinish_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -919,6 +931,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteJobStart(final String name, final int uid) {
+ super.noteJobStart_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -934,6 +948,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteJobFinish(final String name, final int uid, final int stopReason) {
+ super.noteJobFinish_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1007,6 +1023,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStartWakelock(final int uid, final int pid, final String name,
final String historyName, final int type, final boolean unimportantForLogging) {
+ super.noteStartWakelock_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1023,6 +1041,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStopWakelock(final int uid, final int pid, final String name,
final String historyName, final int type) {
+ super.noteStopWakelock_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1039,6 +1059,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStartWakelockFromSource(final WorkSource ws, final int pid, final String name,
final String historyName, final int type, final boolean unimportantForLogging) {
+ super.noteStartWakelockFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1058,6 +1080,8 @@
final String historyName, final int type, final WorkSource newWs, final int newPid,
final String newName, final String newHistoryName, final int newType,
final boolean newUnimportantForLogging) {
+ super.noteChangeWakelockFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
synchronized (mLock) {
@@ -1077,6 +1101,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStopWakelockFromSource(final WorkSource ws, final int pid, final String name,
final String historyName, final int type) {
+ super.noteStopWakelockFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1094,6 +1120,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteLongPartialWakelockStart(final String name, final String historyName,
final int uid) {
+ super.noteLongPartialWakelockStart_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1110,6 +1138,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteLongPartialWakelockStartFromSource(final String name, final String historyName,
final WorkSource workSource) {
+ super.noteLongPartialWakelockStartFromSource_enforcePermission();
+
final WorkSource localWs = workSource != null ? new WorkSource(workSource) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1127,6 +1157,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteLongPartialWakelockFinish(final String name, final String historyName,
final int uid) {
+ super.noteLongPartialWakelockFinish_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1143,6 +1175,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteLongPartialWakelockFinishFromSource(final String name, final String historyName,
final WorkSource workSource) {
+ super.noteLongPartialWakelockFinishFromSource_enforcePermission();
+
final WorkSource localWs = workSource != null ? new WorkSource(workSource) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1159,6 +1193,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStartSensor(final int uid, final int sensor) {
+ super.noteStartSensor_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1175,6 +1211,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStopSensor(final int uid, final int sensor) {
+ super.noteStopSensor_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1191,6 +1229,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteVibratorOn(final int uid, final long durationMillis) {
+ super.noteVibratorOn_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1205,6 +1245,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteVibratorOff(final int uid) {
+ super.noteVibratorOff_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1219,6 +1261,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteGpsChanged(final WorkSource oldWs, final WorkSource newWs) {
+ super.noteGpsChanged_enforcePermission();
+
final WorkSource localOldWs = oldWs != null ? new WorkSource(oldWs) : null;
final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
synchronized (mLock) {
@@ -1235,6 +1279,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteGpsSignalQuality(final int signalLevel) {
+ super.noteGpsSignalQuality_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1249,6 +1295,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteScreenState(final int state) {
+ super.noteScreenState_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1267,6 +1315,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteScreenBrightness(final int brightness) {
+ super.noteScreenBrightness_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1282,6 +1332,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteUserActivity(final int uid, final int event) {
+ super.noteUserActivity_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1296,6 +1348,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWakeUp(final String reason, final int reasonUid) {
+ super.noteWakeUp_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1310,6 +1364,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteInteractive(final boolean interactive) {
+ super.noteInteractive_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
mHandler.post(() -> {
@@ -1323,6 +1379,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteConnectivityChanged(final int type, final String extra) {
+ super.noteConnectivityChanged_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1338,6 +1396,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteMobileRadioPowerState(final int powerState, final long timestampNs,
final int uid) {
+ super.noteMobileRadioPowerState_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1364,6 +1424,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void notePhoneOn() {
+ super.notePhoneOn_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1378,6 +1440,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void notePhoneOff() {
+ super.notePhoneOff_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1392,6 +1456,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void notePhoneSignalStrength(final SignalStrength signalStrength) {
+ super.notePhoneSignalStrength_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1407,6 +1473,8 @@
@EnforcePermission(UPDATE_DEVICE_STATS)
public void notePhoneDataConnectionState(final int dataType, final boolean hasData,
final int serviceType, final int nrFrequency) {
+ super.notePhoneDataConnectionState_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1422,6 +1490,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void notePhoneState(final int state) {
+ super.notePhoneState_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1437,6 +1507,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiOn() {
+ super.noteWifiOn_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1453,6 +1525,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiOff() {
+ super.noteWifiOff_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1469,6 +1543,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStartAudio(final int uid) {
+ super.noteStartAudio_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1485,6 +1561,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStopAudio(final int uid) {
+ super.noteStopAudio_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1501,6 +1579,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStartVideo(final int uid) {
+ super.noteStartVideo_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1517,6 +1597,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStopVideo(final int uid) {
+ super.noteStopVideo_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1533,6 +1615,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteResetAudio() {
+ super.noteResetAudio_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1549,6 +1633,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteResetVideo() {
+ super.noteResetVideo_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1565,6 +1651,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteFlashlightOn(final int uid) {
+ super.noteFlashlightOn_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1581,6 +1669,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteFlashlightOff(final int uid) {
+ super.noteFlashlightOff_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1597,6 +1687,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStartCamera(final int uid) {
+ super.noteStartCamera_enforcePermission();
+
if (DBG) Slog.d(TAG, "begin noteStartCamera");
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1615,6 +1707,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteStopCamera(final int uid) {
+ super.noteStopCamera_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1631,6 +1725,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteResetCamera() {
+ super.noteResetCamera_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1647,6 +1743,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteResetFlashlight() {
+ super.noteResetFlashlight_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1663,6 +1761,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiRadioPowerState(final int powerState, final long tsNanos, final int uid) {
+ super.noteWifiRadioPowerState_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1694,6 +1794,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiRunning(final WorkSource ws) {
+ super.noteWifiRunning_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1712,6 +1814,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiRunningChanged(final WorkSource oldWs, final WorkSource newWs) {
+ super.noteWifiRunningChanged_enforcePermission();
+
final WorkSource localOldWs = oldWs != null ? new WorkSource(oldWs) : null;
final WorkSource localNewWs = newWs != null ? new WorkSource(newWs) : null;
synchronized (mLock) {
@@ -1733,6 +1837,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiStopped(final WorkSource ws) {
+ super.noteWifiStopped_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : ws;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1750,6 +1856,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiState(final int wifiState, final String accessPoint) {
+ super.noteWifiState_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
mHandler.post(() -> {
@@ -1763,6 +1871,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiSupplicantStateChanged(final int supplState, final boolean failedAuth) {
+ super.noteWifiSupplicantStateChanged_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1778,6 +1888,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiRssiChanged(final int newRssi) {
+ super.noteWifiRssiChanged_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1792,6 +1904,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteFullWifiLockAcquired(final int uid) {
+ super.noteFullWifiLockAcquired_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1806,6 +1920,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteFullWifiLockReleased(final int uid) {
+ super.noteFullWifiLockReleased_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1820,6 +1936,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiScanStarted(final int uid) {
+ super.noteWifiScanStarted_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1834,6 +1952,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiScanStopped(final int uid) {
+ super.noteWifiScanStopped_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1848,6 +1968,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiMulticastEnabled(final int uid) {
+ super.noteWifiMulticastEnabled_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1862,6 +1984,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiMulticastDisabled(final int uid) {
+ super.noteWifiMulticastDisabled_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -1876,6 +2000,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteFullWifiLockAcquiredFromSource(final WorkSource ws) {
+ super.noteFullWifiLockAcquiredFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1892,6 +2018,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteFullWifiLockReleasedFromSource(final WorkSource ws) {
+ super.noteFullWifiLockReleasedFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1908,6 +2036,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiScanStartedFromSource(final WorkSource ws) {
+ super.noteWifiScanStartedFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1923,6 +2053,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiScanStoppedFromSource(final WorkSource ws) {
+ super.noteWifiScanStoppedFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1938,6 +2070,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiBatchedScanStartedFromSource(final WorkSource ws, final int csph) {
+ super.noteWifiBatchedScanStartedFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1954,6 +2088,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiBatchedScanStoppedFromSource(final WorkSource ws) {
+ super.noteWifiBatchedScanStoppedFromSource_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -1970,6 +2106,8 @@
@Override
@EnforcePermission(anyOf = {NETWORK_STACK, PERMISSION_MAINLINE_NETWORK_STACK})
public void noteNetworkInterfaceForTransports(final String iface, int[] transportTypes) {
+ super.noteNetworkInterfaceForTransports_enforcePermission();
+
synchronized (mLock) {
mHandler.post(() -> {
mStats.noteNetworkInterfaceForTransports(iface, transportTypes);
@@ -1983,6 +2121,8 @@
// During device boot, qtaguid isn't enabled until after the inital
// loading of battery stats. Now that they're enabled, take our initial
// snapshot for future delta calculation.
+ super.noteNetworkStatsEnabled_enforcePermission();
+
synchronized (mLock) {
// Still schedule it on the handler to make sure we have existing pending works done
mHandler.post(() -> {
@@ -1996,6 +2136,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteDeviceIdleMode(final int mode, final String activeReason, final int activeUid) {
+ super.noteDeviceIdleMode_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -2039,6 +2181,8 @@
@Override
@EnforcePermission(BLUETOOTH_CONNECT)
public void noteBluetoothOn(int uid, int reason, String packageName) {
+ super.noteBluetoothOn_enforcePermission();
+
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
Binder.getCallingUid(), null,
FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED,
@@ -2051,6 +2195,8 @@
@Override
@EnforcePermission(BLUETOOTH_CONNECT)
public void noteBluetoothOff(int uid, int reason, String packageName) {
+ super.noteBluetoothOff_enforcePermission();
+
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
Binder.getCallingUid(), null,
FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED,
@@ -2060,6 +2206,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteBleScanStarted(final WorkSource ws, final boolean isUnoptimized) {
+ super.noteBleScanStarted_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2076,6 +2224,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteBleScanStopped(final WorkSource ws, final boolean isUnoptimized) {
+ super.noteBleScanStopped_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2092,6 +2242,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteBleScanReset() {
+ super.noteBleScanReset_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -2106,6 +2258,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteBleScanResults(final WorkSource ws, final int numNewResults) {
+ super.noteBleScanResults_enforcePermission();
+
final WorkSource localWs = ws != null ? new WorkSource(ws) : null;
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -2122,6 +2276,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteWifiControllerActivity(final WifiActivityEnergyInfo info) {
+ super.noteWifiControllerActivity_enforcePermission();
+
if (info == null || !info.isValid()) {
Slog.e(TAG, "invalid wifi data given: " + info);
return;
@@ -2142,6 +2298,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteBluetoothControllerActivity(final BluetoothActivityEnergyInfo info) {
+ super.noteBluetoothControllerActivity_enforcePermission();
+
if (info == null || !info.isValid()) {
Slog.e(TAG, "invalid bluetooth data given: " + info);
return;
@@ -2162,6 +2320,8 @@
@Override
@EnforcePermission(UPDATE_DEVICE_STATS)
public void noteModemControllerActivity(final ModemActivityInfo info) {
+ super.noteModemControllerActivity_enforcePermission();
+
if (info == null) {
Slog.e(TAG, "invalid modem data given: " + info);
return;
@@ -2188,6 +2348,8 @@
public void setBatteryState(final int status, final int health, final int plugType,
final int level, final int temp, final int volt, final int chargeUAh,
final int chargeFullUAh, final long chargeTimeToFullSeconds) {
+ super.setBatteryState_enforcePermission();
+
synchronized (mLock) {
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long uptime = SystemClock.uptimeMillis();
@@ -2230,12 +2392,16 @@
@Override
@EnforcePermission(BATTERY_STATS)
public long getAwakeTimeBattery() {
+ super.getAwakeTimeBattery_enforcePermission();
+
return mStats.getAwakeTimeBattery();
}
@Override
@EnforcePermission(BATTERY_STATS)
public long getAwakeTimePlugged() {
+ super.getAwakeTimePlugged_enforcePermission();
+
return mStats.getAwakeTimePlugged();
}
@@ -2738,6 +2904,8 @@
@EnforcePermission(anyOf = {UPDATE_DEVICE_STATS, BATTERY_STATS})
public CellularBatteryStats getCellularBatteryStats() {
// Wait for the completion of pending works if there is any
+ super.getCellularBatteryStats_enforcePermission();
+
awaitCompletion();
synchronized (mStats) {
return mStats.getCellularBatteryStats();
@@ -2752,6 +2920,8 @@
@EnforcePermission(anyOf = {UPDATE_DEVICE_STATS, BATTERY_STATS})
public WifiBatteryStats getWifiBatteryStats() {
// Wait for the completion of pending works if there is any
+ super.getWifiBatteryStats_enforcePermission();
+
awaitCompletion();
synchronized (mStats) {
return mStats.getWifiBatteryStats();
@@ -2766,6 +2936,8 @@
@EnforcePermission(BATTERY_STATS)
public GpsBatteryStats getGpsBatteryStats() {
// Wait for the completion of pending works if there is any
+ super.getGpsBatteryStats_enforcePermission();
+
awaitCompletion();
synchronized (mStats) {
return mStats.getGpsBatteryStats();
@@ -2780,6 +2952,8 @@
@EnforcePermission(BATTERY_STATS)
public WakeLockStats getWakeLockStats() {
// Wait for the completion of pending works if there is any
+ super.getWakeLockStats_enforcePermission();
+
awaitCompletion();
synchronized (mStats) {
return mStats.getWakeLockStats();
@@ -2794,6 +2968,8 @@
@EnforcePermission(BATTERY_STATS)
public BluetoothBatteryStats getBluetoothBatteryStats() {
// Wait for the completion of pending works if there is any
+ super.getBluetoothBatteryStats_enforcePermission();
+
awaitCompletion();
synchronized (mStats) {
return mStats.getBluetoothBatteryStats();
@@ -2901,6 +3077,8 @@
*/
@EnforcePermission(POWER_SAVER)
public boolean setChargingStateUpdateDelayMillis(int delayMillis) {
+ super.setChargingStateUpdateDelayMillis_enforcePermission();
+
final long ident = Binder.clearCallingIdentity();
try {
@@ -3051,6 +3229,8 @@
@Override
@EnforcePermission(DEVICE_POWER)
public void setChargerAcOnline(boolean online, boolean forceUpdate) {
+ super.setChargerAcOnline_enforcePermission();
+
mBatteryManagerInternal.setChargerAcOnline(online, forceUpdate);
}
@@ -3060,6 +3240,8 @@
@Override
@EnforcePermission(DEVICE_POWER)
public void setBatteryLevel(int level, boolean forceUpdate) {
+ super.setBatteryLevel_enforcePermission();
+
mBatteryManagerInternal.setBatteryLevel(level, forceUpdate);
}
@@ -3069,6 +3251,8 @@
@Override
@EnforcePermission(DEVICE_POWER)
public void unplugBattery(boolean forceUpdate) {
+ super.unplugBattery_enforcePermission();
+
mBatteryManagerInternal.unplugBattery(forceUpdate);
}
@@ -3078,6 +3262,8 @@
@Override
@EnforcePermission(DEVICE_POWER)
public void resetBattery(boolean forceUpdate) {
+ super.resetBattery_enforcePermission();
+
mBatteryManagerInternal.resetBattery(forceUpdate);
}
@@ -3087,6 +3273,8 @@
@Override
@EnforcePermission(DEVICE_POWER)
public void suspendBatteryInput() {
+ super.suspendBatteryInput_enforcePermission();
+
mBatteryManagerInternal.suspendBatteryInput();
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 2e12309..c994f13 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -135,14 +135,6 @@
private int mActiveIndex;
/**
- * When defined, the receiver actively being dispatched into this process
- * was considered "blocked" until at least the given count of other
- * receivers have reached a terminal state; typically used for ordered
- * broadcasts and priority traunches.
- */
- private int mActiveBlockedUntilTerminalCount;
-
- /**
* Count of {@link #mActive} broadcasts that have been dispatched since this
* queue was last idle.
*/
@@ -206,15 +198,11 @@
* given count of other receivers have reached a terminal state; typically
* used for ordered broadcasts and priority traunches.
*/
- public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
- int blockedUntilTerminalCount) {
+ public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
if (record.isReplacePending()) {
- boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex,
- blockedUntilTerminalCount)
- || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex,
- blockedUntilTerminalCount)
- || replaceBroadcastInQueue(mPendingOffload, record, recordIndex,
- blockedUntilTerminalCount);
+ boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex)
+ || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex)
+ || replaceBroadcastInQueue(mPendingOffload, record, recordIndex);
if (didReplace) {
return;
}
@@ -225,7 +213,6 @@
SomeArgs newBroadcastArgs = SomeArgs.obtain();
newBroadcastArgs.arg1 = record;
newBroadcastArgs.argi1 = recordIndex;
- newBroadcastArgs.argi2 = blockedUntilTerminalCount;
// Cross-broadcast prioritization policy: some broadcasts might warrant being
// issued ahead of others that are already pending, for example if this new
@@ -244,7 +231,7 @@
* {@code false} otherwise.
*/
private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
- @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
+ @NonNull BroadcastRecord record, int recordIndex) {
final Iterator<SomeArgs> it = queue.descendingIterator();
final Object receiver = record.receivers.get(recordIndex);
while (it.hasNext()) {
@@ -259,7 +246,6 @@
// Exact match found; perform in-place swap
args.arg1 = record;
args.argi1 = recordIndex;
- args.argi2 = blockedUntilTerminalCount;
onBroadcastDequeued(testRecord, testRecordIndex);
onBroadcastEnqueued(record, recordIndex);
return true;
@@ -411,7 +397,6 @@
final SomeArgs next = removeNextBroadcast();
mActive = (BroadcastRecord) next.arg1;
mActiveIndex = next.argi1;
- mActiveBlockedUntilTerminalCount = next.argi2;
mActiveCountSinceIdle++;
mActiveViaColdStart = false;
next.recycle();
@@ -424,7 +409,6 @@
public void makeActiveIdle() {
mActive = null;
mActiveIndex = 0;
- mActiveBlockedUntilTerminalCount = -1;
mActiveCountSinceIdle = 0;
mActiveViaColdStart = false;
invalidateRunnableAt();
@@ -705,7 +689,7 @@
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
- final int blockedUntilTerminalCount = next.argi2;
+ final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
final long runnableAt = r.enqueueTime;
// We might be blocked waiting for other receivers to finish,
@@ -871,19 +855,19 @@
pw.println();
pw.increaseIndent();
if (mActive != null) {
- dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
+ dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
}
for (SomeArgs args : mPendingUrgent) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
- dumpRecord("URGENT", now, pw, r, args.argi1, args.argi2);
+ dumpRecord("URGENT", now, pw, r, args.argi1);
}
for (SomeArgs args : mPending) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
- dumpRecord(null, now, pw, r, args.argi1, args.argi2);
+ dumpRecord(null, now, pw, r, args.argi1);
}
for (SomeArgs args : mPendingOffload) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
- dumpRecord("OFFLOAD", now, pw, r, args.argi1, args.argi2);
+ dumpRecord("OFFLOAD", now, pw, r, args.argi1);
}
pw.decreaseIndent();
pw.println();
@@ -891,8 +875,7 @@
@NeverCompile
private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
- @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex,
- int blockedUntilTerminalCount) {
+ @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
TimeUtils.formatDuration(record.enqueueTime, now, pw);
pw.print(' ');
pw.println(record.toShortString());
@@ -918,6 +901,7 @@
pw.print(info.activityInfo.name);
}
pw.println();
+ final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
if (blockedUntilTerminalCount != -1) {
pw.print(" blocked until ");
pw.print(blockedUntilTerminalCount);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index e0fab2c..153ad1e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -77,6 +77,18 @@
}
}
+ static void checkState(boolean expression, @NonNull String msg) {
+ if (!expression) {
+ throw new IllegalStateException(msg);
+ }
+ }
+
+ static void checkStateWtf(boolean expression, @NonNull String msg) {
+ if (!expression) {
+ Slog.wtf(TAG, new IllegalStateException(msg));
+ }
+ }
+
static int traceBegin(@NonNull String methodName) {
final int cookie = methodName.hashCode();
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index ffc54d9..454b284 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1690,13 +1690,7 @@
System.identityHashCode(original));
}
- final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
- final String callerPackage = info != null ? info.packageName : original.callerPackage;
- if (callerPackage != null) {
- mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
- original.callingUid, 0, callerPackage).sendToTarget();
- }
-
+ mService.notifyBroadcastFinishedLocked(original);
mHistory.addBroadcastToHistoryLocked(original);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index af2a97e..c3839a9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -25,14 +25,12 @@
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
-import static com.android.server.am.BroadcastRecord.getReceiverPriority;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
@@ -321,7 +319,6 @@
return;
}
- final int cookie = traceBegin("updateRunnableList");
final boolean wantQueue = queue.isRunnable();
final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
|| (queue.runnableAtNext != null);
@@ -348,8 +345,6 @@
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
}
-
- traceEnd(cookie);
}
/**
@@ -592,36 +587,11 @@
r.enqueueRealTime = SystemClock.elapsedRealtime();
r.enqueueClockTime = System.currentTimeMillis();
- int lastPriority = 0;
- int lastPriorityIndex = 0;
-
for (int i = 0; i < r.receivers.size(); i++) {
final Object receiver = r.receivers.get(i);
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
getReceiverProcessName(receiver), getReceiverUid(receiver));
-
- final int blockedUntilTerminalCount;
- if (r.ordered) {
- // When sending an ordered broadcast, we need to block this
- // receiver until all previous receivers have terminated
- blockedUntilTerminalCount = i;
- } else if (r.prioritized) {
- // When sending a prioritized broadcast, we only need to wait
- // for the previous traunch of receivers to be terminated
- final int thisPriority = getReceiverPriority(receiver);
- if ((i == 0) || (thisPriority != lastPriority)) {
- lastPriority = thisPriority;
- lastPriorityIndex = i;
- blockedUntilTerminalCount = i;
- } else {
- blockedUntilTerminalCount = lastPriorityIndex;
- }
- } else {
- // Otherwise we don't need to block at all
- blockedUntilTerminalCount = -1;
- }
-
- queue.enqueueOrReplaceBroadcast(r, i, blockedUntilTerminalCount);
+ queue.enqueueOrReplaceBroadcast(r, i);
updateRunnableList(queue);
enqueueUpdateRunningList();
}
@@ -757,7 +727,7 @@
}
final Intent receiverIntent = r.getReceiverIntent(receiver);
if (receiverIntent == null) {
- enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
+ enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
return;
}
@@ -917,9 +887,12 @@
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
@DeliveryState int deliveryState, @NonNull String reason) {
- final int cookie = traceBegin("finishReceiver");
- checkState(queue.isActive(), "isActive");
+ if (!queue.isActive()) {
+ logw("Ignoring finish; no active broadcast for " + queue);
+ return false;
+ }
+ final int cookie = traceBegin("finishReceiver");
final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
@@ -1429,6 +1402,7 @@
final boolean recordFinished = (r.terminalCount == r.receivers.size());
if (recordFinished) {
+ mService.notifyBroadcastFinishedLocked(r);
mHistory.addBroadcastToHistoryLocked(r);
r.finishTime = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 65f9b9b..6ea2dee 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -96,6 +96,7 @@
final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
+ final int[] blockedUntilTerminalCount; // blocked until count of each receiver
@Nullable ProcessRecord resultToApp; // who receives final result if non-null
@Nullable IIntentReceiver resultTo; // who receives final result if non-null
boolean deferred;
@@ -375,6 +376,7 @@
options = _options;
receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
delivery = new int[_receivers != null ? _receivers.size() : 0];
+ blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
resultToApp = _resultToApp;
@@ -385,7 +387,7 @@
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
- prioritized = isPrioritized(receivers);
+ prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
userId = _userId;
nextReceiver = 0;
state = IDLE;
@@ -427,6 +429,7 @@
options = from.options;
receivers = from.receivers;
delivery = from.delivery;
+ blockedUntilTerminalCount = from.blockedUntilTerminalCount;
scheduledTime = from.scheduledTime;
terminalTime = from.terminalTime;
resultToApp = from.resultToApp;
@@ -690,22 +693,60 @@
}
/**
- * Return if given receivers list has more than one traunch of priorities.
+ * Determine if the result of {@link #calculateBlockedUntilTerminalCount}
+ * has prioritized tranches of receivers.
*/
@VisibleForTesting
- static boolean isPrioritized(@NonNull List<Object> receivers) {
- int firstPriority = 0;
- for (int i = 0; i < receivers.size(); i++) {
- final int thisPriority = getReceiverPriority(receivers.get(i));
- if (i == 0) {
- firstPriority = thisPriority;
- } else if (thisPriority != firstPriority) {
- return true;
- }
- }
- return false;
+ static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+ boolean ordered) {
+ return !ordered && (blockedUntilTerminalCount.length > 0)
+ && (blockedUntilTerminalCount[0] != -1);
}
+ /**
+ * Calculate the {@link #terminalCount} that each receiver should be
+ * considered blocked until.
+ * <p>
+ * For example, in an ordered broadcast, receiver {@code N} is blocked until
+ * receiver {@code N-1} reaches a terminal state. Similarly, in a
+ * prioritized broadcast, receiver {@code N} is blocked until all receivers
+ * of a higher priority reach a terminal state.
+ * <p>
+ * When there are no terminal count constraints, the blocked value for each
+ * receiver is {@code -1}.
+ */
+ @VisibleForTesting
+ static @NonNull int[] calculateBlockedUntilTerminalCount(
+ @NonNull List<Object> receivers, boolean ordered) {
+ final int N = receivers.size();
+ final int[] blockedUntilTerminalCount = new int[N];
+ int lastPriority = 0;
+ int lastPriorityIndex = 0;
+ for (int i = 0; i < N; i++) {
+ if (ordered) {
+ // When sending an ordered broadcast, we need to block this
+ // receiver until all previous receivers have terminated
+ blockedUntilTerminalCount[i] = i;
+ } else {
+ // When sending a prioritized broadcast, we only need to wait
+ // for the previous tranche of receivers to be terminated
+ final int thisPriority = getReceiverPriority(receivers.get(i));
+ if ((i == 0) || (thisPriority != lastPriority)) {
+ lastPriority = thisPriority;
+ lastPriorityIndex = i;
+ blockedUntilTerminalCount[i] = i;
+ } else {
+ blockedUntilTerminalCount[i] = lastPriorityIndex;
+ }
+ }
+ }
+ // If the entire list is in the same priority tranche, mark as -1 to
+ // indicate that none of them need to wait
+ if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
+ Arrays.fill(blockedUntilTerminalCount, -1);
+ }
+ return blockedUntilTerminalCount;
+ }
static int getReceiverUid(@NonNull Object receiver) {
if (receiver instanceof BroadcastFilter) {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index dec8b62..60e6754 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -116,7 +116,7 @@
30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
30087 ssm_user_stopped (userId|1|5)
30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
-30089 ssm_user_visible (userId|1|5)
+30089 ssm_user_visibility_changed (userId|1|5),(visible|1)
# Foreground service start/stop events.
30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index f461f3d..68d906b 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -44,7 +44,7 @@
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
@@ -62,8 +62,12 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
+
/**
* The error state of the process, such as if it's crashing/ANR etc.
*/
@@ -259,12 +263,13 @@
void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
String parentShortComponentName, WindowProcessController parentProcess,
boolean aboveSystem, TimeoutRecord timeoutRecord,
- boolean onlyDumpSelf) {
+ ExecutorService auxiliaryTaskExecutor, boolean onlyDumpSelf) {
String annotation = timeoutRecord.mReason;
AnrLatencyTracker latencyTracker = timeoutRecord.mLatencyTracker;
+ Future<?> updateCpuStatsNowFirstCall = null;
ArrayList<Integer> firstPids = new ArrayList<>(5);
- SparseArray<Boolean> lastPids = new SparseArray<>(20);
+ SparseBooleanArray lastPids = new SparseBooleanArray(20);
mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
latencyTracker.waitingOnAMSLockStarted();
@@ -277,10 +282,15 @@
});
long anrTime = SystemClock.uptimeMillis();
+
if (isMonitorCpuUsage()) {
- latencyTracker.updateCpuStatsNowCalled();
- mService.updateCpuStatsNow();
- latencyTracker.updateCpuStatsNowReturned();
+ updateCpuStatsNowFirstCall = auxiliaryTaskExecutor.submit(
+ () -> {
+ latencyTracker.updateCpuStatsNowCalled();
+ mService.updateCpuStatsNow();
+ latencyTracker.updateCpuStatsNowReturned();
+ });
+
}
final boolean isSilentAnr;
@@ -369,7 +379,7 @@
firstPids.add(myPid);
if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
} else {
- lastPids.put(myPid, Boolean.TRUE);
+ lastPids.put(myPid, true);
if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
}
}
@@ -432,30 +442,40 @@
report.append(currentPsiState);
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
- latencyTracker.nativePidCollectionStarted();
- // don't dump native PIDs for background ANRs unless it is the process of interest
- String[] nativeProcs = null;
- if (isSilentAnr || onlyDumpSelf) {
- for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
- if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
- nativeProcs = new String[] { mApp.processName };
- break;
- }
- }
- } else {
- nativeProcs = NATIVE_STACKS_OF_INTEREST;
- }
+ // We push the native pids collection task to the helper thread through
+ // the Anr auxiliary task executor, and wait on it later after dumping the first pids
+ Future<ArrayList<Integer>> nativePidsFuture =
+ auxiliaryTaskExecutor.submit(
+ () -> {
+ latencyTracker.nativePidCollectionStarted();
+ // don't dump native PIDs for background ANRs unless
+ // it is the process of interest
+ String[] nativeProcs = null;
+ if (isSilentAnr || onlyDumpSelf) {
+ for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
+ if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
+ nativeProcs = new String[] { mApp.processName };
+ break;
+ }
+ }
+ } else {
+ nativeProcs = NATIVE_STACKS_OF_INTEREST;
+ }
- int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
- ArrayList<Integer> nativePids = null;
+ int[] pids = nativeProcs == null
+ ? null : Process.getPidsForCommands(nativeProcs);
+ ArrayList<Integer> nativePids = null;
- if (pids != null) {
- nativePids = new ArrayList<>(pids.length);
- for (int i : pids) {
- nativePids.add(i);
- }
- }
- latencyTracker.nativePidCollectionEnded();
+ if (pids != null) {
+ nativePids = new ArrayList<>(pids.length);
+ for (int i : pids) {
+ nativePids.add(i);
+ }
+ }
+ latencyTracker.nativePidCollectionEnded();
+ return nativePids;
+ });
+
// For background ANRs, don't pass the ProcessCpuTracker to
// avoid spending 1/2 second collecting stats to rank lastPids.
StringWriter tracesFileException = new StringWriter();
@@ -463,10 +483,18 @@
final AtomicLong firstPidEndOffset = new AtomicLong(-1);
File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
- nativePids, tracesFileException, firstPidEndOffset, annotation, criticalEventLog,
- latencyTracker);
+ nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
+ criticalEventLog, auxiliaryTaskExecutor, latencyTracker);
if (isMonitorCpuUsage()) {
+ // Wait for the first call to finish
+ try {
+ updateCpuStatsNowFirstCall.get();
+ } catch (ExecutionException e) {
+ Slog.w(TAG, "Failed to update the CPU stats", e.getCause());
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while updating the CPU stats", e);
+ }
mService.updateCpuStatsNow();
mService.mAppProfiler.printCurrentCpuState(report, anrTime);
info.append(processCpuTracker.printCurrentLoad());
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8d3890c..af55980 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2190,7 +2190,7 @@
if (oldUserId == UserHandle.USER_SYSTEM) {
// System user is never stopped, but its visibility is changed (as it is brought to the
// background)
- updateSystemUserVisibility(/* visible= */ false);
+ updateSystemUserVisibility(t, /* visible= */ false);
}
t.traceEnd(); // end continueUserSwitch
@@ -2549,10 +2549,15 @@
// TODO(b/242195409): remove this method if initial system user boot logic is refactored?
void onSystemUserStarting() {
- updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode());
+ if (!UserManager.isHeadlessSystemUserMode()) {
+ // Don't need to call on HSUM because it will be called when the system user is
+ // restarted on background
+ mInjector.onUserStarting(UserHandle.USER_SYSTEM, /* visible= */ true);
+ }
}
- private void updateSystemUserVisibility(boolean visible) {
+ private void updateSystemUserVisibility(TimingsTraceAndSlog t, boolean visible) {
+ t.traceBegin("update-system-userVisibility-" + visible);
if (DEBUG_MU) {
Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
}
@@ -2564,7 +2569,8 @@
mVisibleUsers.delete(userId);
}
}
- mInjector.onUserStarting(userId, visible);
+ mInjector.notifySystemUserVisibilityChanged(visible);
+ t.traceEnd();
}
/**
@@ -3673,5 +3679,8 @@
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
visible);
}
+ void notifySystemUserVisibilityChanged(boolean visible) {
+ getSystemServiceManager().onSystemUserVisibilityChanged(visible);
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index bbffc89..2fe06094 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -286,22 +286,9 @@
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
}
-
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- AudioDeviceAttributes device = null;
- if (on) {
- device = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
- } else {
- CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
- if (client == null || !client.requestsSpeakerphone()) {
- return;
- }
- }
- postSetCommunicationRouteForClient(new CommunicationClientInfo(
- cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource));
- }
- }
+ postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+ cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+ on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
}
/**
@@ -311,6 +298,9 @@
* @param device Device selected or null to unselect.
* @param eventSource for logging purposes
*/
+
+ private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
+
/*package*/ boolean setCommunicationDevice(
IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
@@ -318,21 +308,53 @@
Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
}
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- AudioDeviceAttributes deviceAttr = null;
- if (device != null) {
- deviceAttr = new AudioDeviceAttributes(device);
- } else {
- CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
- if (client == null) {
- return false;
+ AudioDeviceAttributes deviceAttr =
+ (device != null) ? new AudioDeviceAttributes(device) : null;
+ CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
+ device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
+ postSetCommunicationDeviceForClient(deviceInfo);
+ boolean status;
+ synchronized (deviceInfo) {
+ final long start = System.currentTimeMillis();
+ long elapsed = 0;
+ while (deviceInfo.mWaitForStatus) {
+ try {
+ deviceInfo.wait(SET_COMMUNICATION_DEVICE_TIMEOUT_MS - elapsed);
+ } catch (InterruptedException e) {
+ elapsed = System.currentTimeMillis() - start;
+ if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) {
+ deviceInfo.mStatus = false;
+ deviceInfo.mWaitForStatus = false;
}
}
- postSetCommunicationRouteForClient(new CommunicationClientInfo(
- cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource));
+ }
+ status = deviceInfo.mStatus;
+ }
+ return status;
+ }
+
+ /**
+ * Sets or resets the communication device for matching client. If no client matches and the
+ * request is to reset for a given device (deviceInfo.mOn == false), the method is a noop.
+ * @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo}
+ * @return true if the communication device is set or reset
+ */
+ @GuardedBy("mDeviceStateLock")
+ /*package*/ boolean onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
+ }
+ if (!deviceInfo.mOn) {
+ CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
+ if (client == null || (deviceInfo.mDevice != null
+ && !deviceInfo.mDevice.equals(client.getDevice()))) {
+ return false;
}
}
+
+ AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
+ setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
+ deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
return true;
}
@@ -390,7 +412,7 @@
mBtHelper.stopBluetoothSco(eventSource);
}
- sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
+ updateCommunicationRoute(eventSource);
}
/**
@@ -424,7 +446,7 @@
CommunicationRouteClient crc = topCommunicationRouteClient();
AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "requestedCommunicationDevice, device: "
+ Log.v(TAG, "requestedCommunicationDevice: "
+ device + " mAudioModeOwner: " + mAudioModeOwner.toString());
}
return device;
@@ -822,37 +844,22 @@
@NonNull String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "startBluetoothScoForClient_Sync, pid: " + pid);
+ Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
}
-
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- AudioDeviceAttributes device =
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
-
- postSetCommunicationRouteForClient(new CommunicationClientInfo(
- cb, pid, device, scoAudioMode, eventSource));
- }
- }
+ postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+ cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+ true, scoAudioMode, eventSource, false));
}
/*package*/ void stopBluetoothScoForClient(
IBinder cb, int pid, @NonNull String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "stopBluetoothScoForClient_Sync, pid: " + pid);
+ Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
}
-
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
- if (client == null || !client.requestsBluetoothSco()) {
- return;
- }
- postSetCommunicationRouteForClient(new CommunicationClientInfo(
- cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource));
- }
- }
+ postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+ cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+ false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
}
/*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -990,7 +997,8 @@
}
//---------------------------------------------------------------------
- // Message handling on behalf of helper classes
+ // Message handling on behalf of helper classes.
+ // Each of these methods posts a message to mBrokerHandler message queue.
/*package*/ void postBroadcastScoConnectionState(int state) {
sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
}
@@ -1046,28 +1054,34 @@
sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource);
}
- /*package*/ void postSetCommunicationRouteForClient(CommunicationClientInfo info) {
- sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT, SENDMSG_QUEUE, info);
+ /*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
+ sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT, SENDMSG_QUEUE, info);
}
/*package*/ void postScoAudioStateChanged(int state) {
sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
}
- /*package*/ static final class CommunicationClientInfo {
- final @NonNull IBinder mCb;
- final int mPid;
- final @NonNull AudioDeviceAttributes mDevice;
- final int mScoAudioMode;
- final @NonNull String mEventSource;
+ /*package*/ static final class CommunicationDeviceInfo {
+ final @NonNull IBinder mCb; // Identifies the requesting client for death handler
+ final int mPid; // Requester process ID
+ final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
+ final boolean mOn; // true if setting, false if resetting
+ final int mScoAudioMode; // only used for SCO: requested audio mode
+ final @NonNull String mEventSource; // caller identifier for logging
+ boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
+ boolean mStatus = false; // completion status only used if mWaitForStatus is true
- CommunicationClientInfo(@NonNull IBinder cb, int pid, @NonNull AudioDeviceAttributes device,
- int scoAudioMode, @NonNull String eventSource) {
+ CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
+ @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
+ @NonNull String eventSource, boolean waitForStatus) {
mCb = cb;
mPid = pid;
mDevice = device;
+ mOn = on;
mScoAudioMode = scoAudioMode;
mEventSource = eventSource;
+ mWaitForStatus = waitForStatus;
}
// redefine equality op so we can match messages intended for this client
@@ -1079,21 +1093,24 @@
if (this == o) {
return true;
}
- if (!(o instanceof CommunicationClientInfo)) {
+ if (!(o instanceof CommunicationDeviceInfo)) {
return false;
}
- return mCb.equals(((CommunicationClientInfo) o).mCb)
- && mPid == ((CommunicationClientInfo) o).mPid;
+ return mCb.equals(((CommunicationDeviceInfo) o).mCb)
+ && mPid == ((CommunicationDeviceInfo) o).mPid;
}
@Override
public String toString() {
- return "CommunicationClientInfo mCb=" + mCb.toString()
- +"mPid=" + mPid
- +"mDevice=" + mDevice.toString()
- +"mScoAudioMode=" + mScoAudioMode
- +"mEventSource=" + mEventSource;
+ return "CommunicationDeviceInfo mCb=" + mCb.toString()
+ + " mPid=" + mPid
+ + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
+ + " mOn=" + mOn
+ + " mScoAudioMode=" + mScoAudioMode
+ + " mEventSource=" + mEventSource
+ + " mWaitForStatus=" + mWaitForStatus
+ + " mStatus=" + mStatus;
}
}
@@ -1297,7 +1314,7 @@
updateActiveCommunicationDevice();
mDeviceInventory.onRestoreDevices();
mBtHelper.onAudioServerDiedRestoreA2dp();
- onUpdateCommunicationRoute("MSG_RESTORE_DEVICES");
+ updateCommunicationRoute("MSG_RESTORE_DEVICES");
}
}
break;
@@ -1392,12 +1409,19 @@
}
break;
- case MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT:
+ case MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT:
+ CommunicationDeviceInfo deviceInfo = (CommunicationDeviceInfo) msg.obj;
+ boolean status;
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- CommunicationClientInfo info = (CommunicationClientInfo) msg.obj;
- setCommunicationRouteForClient(info.mCb, info.mPid, info.mDevice,
- info.mScoAudioMode, info.mEventSource);
+ status = onSetCommunicationDeviceForClient(deviceInfo);
+ }
+ }
+ synchronized (deviceInfo) {
+ if (deviceInfo.mWaitForStatus) {
+ deviceInfo.mStatus = status;
+ deviceInfo.mWaitForStatus = false;
+ deviceInfo.notify();
}
}
break;
@@ -1410,14 +1434,6 @@
}
break;
- case MSG_L_UPDATE_COMMUNICATION_ROUTE:
- synchronized (mSetModeLock) {
- synchronized (mDeviceStateLock) {
- onUpdateCommunicationRoute((String) msg.obj);
- }
- }
- break;
-
case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
@@ -1568,8 +1584,7 @@
private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 37;
private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
- private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39;
- private static final int MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT = 42;
+ private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
@@ -1793,18 +1808,6 @@
AudioDeviceAttributes getDevice() {
return mDevice;
}
-
- boolean requestsBluetoothSco() {
- return mDevice != null
- && mDevice.getType()
- == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
- }
-
- boolean requestsSpeakerphone() {
- return mDevice != null
- && mDevice.getType()
- == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
- }
}
// @GuardedBy("mSetModeLock")
@@ -1852,14 +1855,14 @@
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
- private void onUpdateCommunicationRoute(String eventSource) {
+ private void updateCommunicationRoute(String eventSource) {
AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice();
if (AudioService.DEBUG_COMM_RTE) {
- Log.v(TAG, "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+ Log.v(TAG, "updateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource);
}
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+ "updateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource)));
if (preferredCommunicationDevice == null
@@ -1895,7 +1898,7 @@
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
private void onUpdateCommunicationRouteClient(String eventSource) {
- onUpdateCommunicationRoute(eventSource);
+ updateCommunicationRoute(eventSource);
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRouteClient, crc: "
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f8a6462..4fda233 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,6 +41,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -153,6 +154,7 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.System;
import android.service.notification.ZenModeConfig;
@@ -174,6 +176,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
@@ -233,6 +236,7 @@
AudioSystemAdapter.OnVolRangeInitRequestListener {
private static final String TAG = "AS.AudioService";
+ private static final boolean CONFIG_DEFAULT_VAL = false;
private final AudioSystemAdapter mAudioSystem;
private final SystemServerAdapter mSystemServer;
@@ -987,6 +991,7 @@
* @param looper Looper to use for the service's message handler. If this is null, an
* {@link AudioSystemThread} is created as the messaging thread instead.
*/
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
AppOpsManager appOps) {
@@ -1026,8 +1031,12 @@
mUseVolumeGroupAliases = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
- mNotifAliasRing = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_alias_ring_notif_stream_types);
+ mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ ActivityThread.currentApplication().getMainExecutor(),
+ this::onDeviceConfigChange);
// Initialize volume
// Priority 1 - Android Property
@@ -1246,6 +1255,22 @@
}
/**
+ * Separating notification volume from ring is NOT of aliasing the corresponding streams
+ * @param properties
+ */
+ private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set<String> changeSet = properties.getKeyset();
+ if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+ boolean newNotifAliasRing = !properties.getBoolean(
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+ if (mNotifAliasRing != newNotifAliasRing) {
+ mNotifAliasRing = newNotifAliasRing;
+ updateStreamVolumeAlias(true, TAG);
+ }
+ }
+ }
+
+ /**
* Called by handling of MSG_INIT_STREAMS_VOLUMES
*/
private void onInitStreamsAndVolumes() {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 74bfa80..0bc4b20 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,12 +17,12 @@
package com.android.server.audio;
import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_CLIENT_VOLUME;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_MASTER;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_PLAYBACK_RESTRICTED;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_MUTED;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_VOLUME;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_VOLUME_SHAPER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
@@ -1155,22 +1155,22 @@
if (mEventValue <= 0) {
builder.append("none ");
} else {
- if ((mEventValue & PLAYER_MUTE_MASTER) != 0) {
+ if ((mEventValue & MUTED_BY_MASTER) != 0) {
builder.append("masterMute ");
}
- if ((mEventValue & PLAYER_MUTE_STREAM_VOLUME) != 0) {
+ if ((mEventValue & MUTED_BY_STREAM_VOLUME) != 0) {
builder.append("streamVolume ");
}
- if ((mEventValue & PLAYER_MUTE_STREAM_MUTED) != 0) {
+ if ((mEventValue & MUTED_BY_STREAM_MUTED) != 0) {
builder.append("streamMute ");
}
- if ((mEventValue & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) {
- builder.append("playbackRestricted ");
+ if ((mEventValue & MUTED_BY_APP_OPS) != 0) {
+ builder.append("appOps ");
}
- if ((mEventValue & PLAYER_MUTE_CLIENT_VOLUME) != 0) {
+ if ((mEventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
builder.append("clientVolume ");
}
- if ((mEventValue & PLAYER_MUTE_VOLUME_SHAPER) != 0) {
+ if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
builder.append("volumeShaper ");
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 7f1fb1c..49bd44b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -72,7 +72,6 @@
class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
- private static final int MESSAGE_IGNORE_AUTH = 1;
private static final int MESSAGE_AUTH_SUCCESS = 2;
private static final int MESSAGE_FINGER_UP = 3;
@NonNull
@@ -249,12 +248,6 @@
() -> {
long delay = 0;
if (authenticated && mSensorProps.isAnySidefpsType()) {
- if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
- Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
- 0, true);
- return;
- }
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
if (mSideFpsLastAcquireStartTime != -1) {
@@ -515,18 +508,15 @@
if (mSensorProps.isAnySidefpsType()) {
Slog.i(TAG, "(sideFPS): onPowerPressed");
mHandler.post(() -> {
- if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
- Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
- mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
- // Do not call onError() as that will send an additional callback to coex.
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
- mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
- mBiometricStrength, getSensorId(), getRequestId());
- }
- mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
- mHandler.postDelayed(() -> {
- }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
-
+ Slog.i(TAG, "(sideFPS): finishing auth");
+ // Ignore auths after a power has been detected
+ mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+ // Do not call onError() as that will send an additional callback to coex.
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+ 0, true);
+ mSensorOverlays.hide(getSensorId());
+ mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
+ mBiometricStrength, getSensorId(), getRequestId());
});
}
}
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 11eb782..b882c47 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -841,6 +841,7 @@
streamProtos[i].histogramCounts = streamStats.getHistogramCounts();
streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile();
streamProtos[i].streamUseCase = streamStats.getStreamUseCase();
+ streamProtos[i].colorSpace = streamStats.getColorSpace();
if (CameraServiceProxy.DEBUG) {
String histogramTypeName =
@@ -863,7 +864,8 @@
+ ", histogramCounts "
+ Arrays.toString(streamProtos[i].histogramCounts)
+ ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile
- + ", streamUseCase " + streamProtos[i].streamUseCase);
+ + ", streamUseCase " + streamProtos[i].streamUseCase
+ + ", colorSpace " + streamProtos[i].colorSpace);
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c671a2c..6a000d9 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2928,7 +2928,6 @@
ikeConfiguration.isIkeExtensionEnabled(
IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
- mRetryCount = 0;
}
/**
@@ -3048,6 +3047,7 @@
}
doSendLinkProperties(networkAgent, lp);
+ mRetryCount = 0;
} catch (Exception e) {
Log.d(TAG, "Error in ChildOpened for token " + token, e);
onSessionLost(token, e);
@@ -3345,6 +3345,10 @@
}
private void scheduleRetryNewIkeSession() {
+ if (mScheduledHandleRetryIkeSessionFuture != null) {
+ Log.d(TAG, "There is a pending retrying task, skip the new retrying task");
+ return;
+ }
final long retryDelay = mDeps.getNextRetryDelaySeconds(mRetryCount++);
Log.d(TAG, "Retry new IKE session after " + retryDelay + " seconds.");
// If the default network is lost during the retry delay, the mActiveNetwork will be
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index e9856d0..df4c471 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -1133,7 +1133,7 @@
public void registerReceiver(Context context,
BroadcastReceiver receiver, IntentFilter filter) {
- context.registerReceiver(receiver, filter);
+ context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
}
public void unregisterReceiver(Context context,
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index aa9f2dc..726326f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -33,7 +33,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.net.Uri;
import android.os.Handler;
import android.os.IThermalEventListener;
@@ -2359,39 +2359,39 @@
}
}
- private class UdfpsObserver extends IUdfpsHbmListener.Stub {
- private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray();
+ private class UdfpsObserver extends IUdfpsRefreshRateRequestCallback.Stub {
+ private final SparseBooleanArray mUdfpsRefreshRateEnabled = new SparseBooleanArray();
public void observe() {
StatusBarManagerInternal statusBar =
LocalServices.getService(StatusBarManagerInternal.class);
if (statusBar != null) {
- statusBar.setUdfpsHbmListener(this);
+ statusBar.setUdfpsRefreshRateCallback(this);
}
}
@Override
- public void onHbmEnabled(int displayId) {
+ public void onRequestEnabled(int displayId) {
synchronized (mLock) {
- updateHbmStateLocked(displayId, true /*enabled*/);
+ updateRefreshRateStateLocked(displayId, true /*enabled*/);
}
}
@Override
- public void onHbmDisabled(int displayId) {
+ public void onRequestDisabled(int displayId) {
synchronized (mLock) {
- updateHbmStateLocked(displayId, false /*enabled*/);
+ updateRefreshRateStateLocked(displayId, false /*enabled*/);
}
}
- private void updateHbmStateLocked(int displayId, boolean enabled) {
- mLocalHbmEnabled.put(displayId, enabled);
+ private void updateRefreshRateStateLocked(int displayId, boolean enabled) {
+ mUdfpsRefreshRateEnabled.put(displayId, enabled);
updateVoteLocked(displayId);
}
private void updateVoteLocked(int displayId) {
final Vote vote;
- if (mLocalHbmEnabled.get(displayId)) {
+ if (mUdfpsRefreshRateEnabled.get(displayId)) {
Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
float maxRefreshRate = 0f;
for (Display.Mode mode : modes) {
@@ -2409,10 +2409,10 @@
void dumpLocked(PrintWriter pw) {
pw.println(" UdfpsObserver");
- pw.println(" mLocalHbmEnabled: ");
- for (int i = 0; i < mLocalHbmEnabled.size(); i++) {
- final int displayId = mLocalHbmEnabled.keyAt(i);
- final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
+ pw.println(" mUdfpsRefreshRateEnabled: ");
+ for (int i = 0; i < mUdfpsRefreshRateEnabled.size(); i++) {
+ final int displayId = mUdfpsRefreshRateEnabled.keyAt(i);
+ final String enabled = mUdfpsRefreshRateEnabled.valueAt(i) ? "enabled" : "disabled";
pw.println(" Display " + displayId + ": " + enabled);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 422e98f..1f643d7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2838,18 +2838,22 @@
event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
? -1f : convertToNits(event.getThermalMax());
- FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- convertToNits(event.getInitialBrightness()),
- convertToNits(event.getBrightness()),
- event.getSlowAmbientLux(),
- event.getPhysicalDisplayId(),
- event.isShortTermModelActive(),
- appliedLowPowerMode,
- appliedRbcStrength,
- appliedHbmMaxNits,
- appliedThermalCapNits,
- event.isAutomaticBrightnessEnabled(),
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
+ && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
+ .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+ convertToNits(event.getInitialBrightness()),
+ convertToNits(event.getBrightness()),
+ event.getSlowAmbientLux(),
+ event.getPhysicalDisplayId(),
+ event.isShortTermModelActive(),
+ appliedLowPowerMode,
+ appliedRbcStrength,
+ appliedHbmMaxNits,
+ appliedThermalCapNits,
+ event.isAutomaticBrightnessEnabled(),
+ FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ }
}
private final class DisplayControllerHandler extends Handler {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 4ca4817..87327cb 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -211,7 +211,7 @@
mDozeConfig = new AmbientDisplayConfiguration(mContext);
mUiEventLogger = new UiEventLoggerImpl();
mDreamUiEventLogger = new DreamUiEventLoggerImpl(
- mContext.getResources().getString(R.string.config_loggable_dream_prefix));
+ mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
mDreamsOnlyEnabledForSystemUser =
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
index 26ca74a..96ebcbb 100644
--- a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
@@ -26,10 +26,10 @@
* @hide
*/
public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
- final String mLoggableDreamPrefix;
+ private final String[] mLoggableDreamPrefixes;
- DreamUiEventLoggerImpl(String loggableDreamPrefix) {
- mLoggableDreamPrefix = loggableDreamPrefix;
+ DreamUiEventLoggerImpl(String[] loggableDreamPrefixes) {
+ mLoggableDreamPrefixes = loggableDreamPrefixes;
}
@Override
@@ -38,13 +38,20 @@
if (eventID <= 0) {
return;
}
- final boolean isFirstPartyDream =
- mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith(
- mLoggableDreamPrefix);
FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED,
/* uid = 1 */ 0,
/* event_id = 2 */ eventID,
/* instance_id = 3 */ 0,
- /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other");
+ /* dream_component_name = 4 */
+ isFirstPartyDream(dreamComponentName) ? dreamComponentName : "other");
+ }
+
+ private boolean isFirstPartyDream(String dreamComponentName) {
+ for (int i = 0; i < mLoggableDreamPrefixes.length; ++i) {
+ if (dreamComponentName.startsWith(mLoggableDreamPrefixes[i])) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 565b2ac..4d1c5ae 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3204,7 +3204,7 @@
void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
- throw new IllegalArgumentException("Unknown id: " + id);
+ throw getExceptionForUnknownImeId(id);
}
// See if we need to notify a subtype change within the same IME.
@@ -3925,8 +3925,7 @@
@EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
- public void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode,
- int displayId) {
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
// Always call subtype picker, because subtype picker is a superset of input method
// picker.
super.showInputMethodPickerFromSystem_enforcePermission();
@@ -3947,12 +3946,25 @@
}
}
+ @NonNull
+ private static IllegalArgumentException getExceptionForUnknownImeId(
+ @Nullable String imeId) {
+ return new IllegalArgumentException("Unknown id: " + imeId);
+ }
+
@BinderThread
private void setInputMethod(@NonNull IBinder token, String id) {
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
return;
}
+ final InputMethodInfo imi = mMethodMap.get(id);
+ if (imi == null || !canCallerAccessInputMethod(
+ imi.getPackageName(), callingUid, userId, mSettings)) {
+ throw getExceptionForUnknownImeId(id);
+ }
setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
}
}
@@ -3960,14 +3972,20 @@
@BinderThread
private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
InputMethodSubtype subtype) {
+ final int callingUid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
return;
}
+ final InputMethodInfo imi = mMethodMap.get(id);
+ if (imi == null || !canCallerAccessInputMethod(
+ imi.getPackageName(), callingUid, userId, mSettings)) {
+ throw getExceptionForUnknownImeId(id);
+ }
if (subtype != null) {
setInputMethodWithSubtypeIdLocked(token, id,
- SubtypeUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
- subtype.hashCode()));
+ SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()));
} else {
setInputMethod(token, id);
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 09b031e..90245b5e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -207,6 +207,14 @@
handleClientMessageCallback(mContextHubId, hostEndpointId, message, nanoappPermissions,
messagePermissions);
}
+
+ @Override
+ public void handleServiceRestart() {
+ Log.i(TAG, "Starting Context Hub Service restart");
+ initExistingCallbacks();
+ resetSettings();
+ Log.i(TAG, "Finished Context Hub Service restart");
+ }
}
public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) {
@@ -381,6 +389,20 @@
}
/**
+ * Initializes existing callbacks with the mContextHubWrapper for every context hub
+ */
+ private void initExistingCallbacks() {
+ for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
+ try {
+ mContextHubWrapper.registerExistingCallback(contextHubId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while registering existing service callback for hub "
+ + "(ID = " + contextHubId + ")", e);
+ }
+ }
+ }
+
+ /**
* Handles the initialization of location settings notifications
*/
private void initLocationSettingNotifications() {
@@ -508,6 +530,17 @@
mContext.registerReceiver(btReceiver, filter);
}
+ /**
+ * Resets the settings. Called when a context hub restarts or the AIDL HAL dies
+ */
+ private void resetSettings() {
+ sendLocationSettingUpdate();
+ sendWifiSettingUpdate(/* forceUpdate= */ true);
+ sendAirplaneModeSettingUpdate();
+ sendMicrophoneDisableSettingUpdateForCurrentUser();
+ sendBtSettingUpdate(/* forceUpdate= */ true);
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {
@@ -859,11 +892,7 @@
ContextHubEventLogger.getInstance().logContextHubRestart(contextHubId);
- sendLocationSettingUpdate();
- sendWifiSettingUpdate(/* forceUpdate= */ true);
- sendAirplaneModeSettingUpdate();
- sendMicrophoneDisableSettingUpdateForCurrentUser();
- sendBtSettingUpdate(/* forceUpdate= */ true);
+ resetSettings();
mTransactionManager.onHubReset();
queryNanoAppsInternal(contextHubId);
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 432b097..48152b4 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -32,6 +32,7 @@
import android.hardware.location.NanoAppState;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -92,6 +93,11 @@
*/
void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
List<String> nanoappPermissions, List<String> messagePermissions);
+
+ /**
+ * Handles a restart of the service
+ */
+ void handleServiceRestart();
}
/**
@@ -170,12 +176,9 @@
}
/**
- * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
- *
- * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
+ * Attempts to connect to the AIDL HAL and returns the proxy IContextHub.
*/
- @Nullable
- public static IContextHubWrapper maybeConnectToAidl() {
+ public static android.hardware.contexthub.IContextHub maybeConnectToAidlGetProxy() {
android.hardware.contexthub.IContextHub proxy = null;
final String aidlServiceName =
android.hardware.contexthub.IContextHub.class.getCanonicalName() + "/default";
@@ -188,8 +191,18 @@
} else {
Log.d(TAG, "Context Hub AIDL service is not declared");
}
+ return proxy;
+ }
- return (proxy == null) ? null : new ContextHubWrapperAidl(proxy);
+ /**
+ * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
+ *
+ * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
+ */
+ @Nullable
+ public static IContextHubWrapper maybeConnectToAidl() {
+ android.hardware.contexthub.IContextHub proxy = maybeConnectToAidlGetProxy();
+ return proxy == null ? null : new ContextHubWrapperAidl(proxy);
}
/**
@@ -354,12 +367,22 @@
public abstract void registerCallback(int contextHubId, @NonNull ICallback callback)
throws RemoteException;
- private static class ContextHubWrapperAidl extends IContextHubWrapper {
+ /**
+ * Registers an existing callback with the Context Hub.
+ *
+ * @param contextHubId The ID of the Context Hub to register the callback with.
+ */
+ public abstract void registerExistingCallback(int contextHubId) throws RemoteException;
+
+ private static class ContextHubWrapperAidl extends IContextHubWrapper
+ implements IBinder.DeathRecipient {
private android.hardware.contexthub.IContextHub mHub;
private final Map<Integer, ContextHubAidlCallback> mAidlCallbackMap =
new HashMap<>();
+ private Runnable mHandleServiceRestartCallback = null;
+
// Use this thread in case where the execution requires to be on a service thread.
// For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission.
private HandlerThread mHandlerThread =
@@ -419,17 +442,51 @@
}
ContextHubWrapperAidl(android.hardware.contexthub.IContextHub hub) {
- mHub = hub;
+ setHub(hub);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ linkWrapperToHubDeath();
+ }
+
+ private synchronized android.hardware.contexthub.IContextHub getHub() {
+ return mHub;
+ }
+
+ private synchronized void setHub(android.hardware.contexthub.IContextHub hub) {
+ mHub = hub;
+ }
+
+ @Override
+ public void binderDied() {
+ Log.i(TAG, "Context Hub AIDL HAL died");
+
+ setHub(maybeConnectToAidlGetProxy());
+ if (getHub() == null) {
+ // TODO(b/256860015): Make this reconnection more robust
+ Log.e(TAG, "Could not reconnect to Context Hub AIDL HAL");
+ return;
+ }
+ linkWrapperToHubDeath();
+
+ if (mHandleServiceRestartCallback != null) {
+ mHandleServiceRestartCallback.run();
+ } else {
+ Log.e(TAG, "mHandleServiceRestartCallback is not set");
+ }
}
public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
+ new ArrayList<String>());
+ }
+
Set<String> supportedPermissions = new HashSet<>();
ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
- for (android.hardware.contexthub.ContextHubInfo hub : mHub.getContextHubs()) {
- hubInfoList.add(new ContextHubInfo(hub));
- for (String permission : hub.supportedPermissions) {
+ for (android.hardware.contexthub.ContextHubInfo hubInfo : hub.getContextHubs()) {
+ hubInfoList.add(new ContextHubInfo(hubInfo));
+ for (String permission : hubInfo.supportedPermissions) {
supportedPermissions.add(permission);
}
}
@@ -489,8 +546,13 @@
@Override
public void onHostEndpointConnected(HostEndpointInfo info) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
try {
- mHub.onHostEndpointConnected(info);
+ hub.onHostEndpointConnected(info);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage());
}
@@ -498,8 +560,13 @@
@Override
public void onHostEndpointDisconnected(short hostEndpointId) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
try {
- mHub.onHostEndpointDisconnected((char) hostEndpointId);
+ hub.onHostEndpointDisconnected((char) hostEndpointId);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage());
}
@@ -509,8 +576,13 @@
public int sendMessageToContextHub(
short hostEndpointId, int contextHubId, NanoAppMessage message)
throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.sendMessageToHub(contextHubId,
+ hub.sendMessageToHub(contextHubId,
ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message));
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException e) {
@@ -523,10 +595,15 @@
@ContextHubTransaction.Result
public int loadNanoapp(int contextHubId, NanoAppBinary binary,
int transactionId) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
android.hardware.contexthub.NanoappBinary aidlNanoAppBinary =
ContextHubServiceUtil.createAidlNanoAppBinary(binary);
try {
- mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
+ hub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -538,8 +615,13 @@
@ContextHubTransaction.Result
public int unloadNanoapp(int contextHubId, long nanoappId, int transactionId)
throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.unloadNanoapp(contextHubId, nanoappId, transactionId);
+ hub.unloadNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -551,8 +633,13 @@
@ContextHubTransaction.Result
public int enableNanoapp(int contextHubId, long nanoappId, int transactionId)
throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.enableNanoapp(contextHubId, nanoappId, transactionId);
+ hub.enableNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -564,8 +651,13 @@
@ContextHubTransaction.Result
public int disableNanoapp(int contextHubId, long nanoappId, int transactionId)
throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.disableNanoapp(contextHubId, nanoappId, transactionId);
+ hub.disableNanoapp(contextHubId, nanoappId, transactionId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -576,8 +668,13 @@
@ContextHubTransaction.Result
public int queryNanoapps(int contextHubId) throws RemoteException {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+
try {
- mHub.queryNanoapps(contextHubId);
+ hub.queryNanoapps(contextHubId);
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -586,22 +683,65 @@
}
}
- public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
- mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+ public void registerExistingCallback(int contextHubId) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ ContextHubAidlCallback callback = mAidlCallbackMap.get(contextHubId);
+ if (callback == null) {
+ Log.e(TAG, "Could not find existing callback to register for context hub ID = "
+ + contextHubId);
+ return;
+ }
+
try {
- mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
+ hub.registerCallback(contextHubId, callback);
} catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) {
Log.e(TAG, "Exception while registering callback: " + e.getMessage());
}
}
+ public void registerCallback(int contextHubId, ICallback callback) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ mHandleServiceRestartCallback = callback::handleServiceRestart;
+ mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+ registerExistingCallback(contextHubId);
+ }
+
private void onSettingChanged(byte setting, boolean enabled) {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
try {
- mHub.onSettingChanged(setting, enabled);
+ hub.onSettingChanged(setting, enabled);
} catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Exception while sending setting update: " + e.getMessage());
}
}
+
+ /**
+ * Links the mHub death handler to this
+ */
+ private void linkWrapperToHubDeath() {
+ android.hardware.contexthub.IContextHub hub = getHub();
+ if (hub == null) {
+ return;
+ }
+
+ try {
+ hub.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException exception) {
+ Log.e(TAG, "Context Hub AIDL service death receipt could not be linked");
+ }
+ }
}
/**
@@ -729,6 +869,17 @@
mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId));
}
+ public void registerExistingCallback(int contextHubId) throws RemoteException {
+ ContextHubWrapperHidlCallback callback = mHidlCallbackMap.get(contextHubId);
+ if (callback == null) {
+ Log.e(TAG, "Could not find existing callback for context hub with ID = "
+ + contextHubId);
+ return;
+ }
+
+ mHub.registerCallback(contextHubId, callback);
+ }
+
public boolean supportsBtSettingNotifications() {
return false;
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index db036b0..d836df5 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -36,6 +36,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -52,6 +53,7 @@
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
@@ -308,48 +310,45 @@
private void writeFile(File path, byte[] data) {
synchronized (mFileWriteLock) {
- RandomAccessFile raf = null;
+ // Use AtomicFile to guarantee atomicity of the file write, including when an existing
+ // file is replaced with a new one. This method is usually used to create new files,
+ // but there are some edge cases in which it is used to replace an existing file.
+ AtomicFile file = new AtomicFile(path);
+ FileOutputStream out = null;
try {
- // Write the data to the file, requiring each write to be synchronized to the
- // underlying storage device immediately to avoid data loss in case of power loss.
- raf = new RandomAccessFile(path, "rws");
- // Truncate the file if the data is empty.
- if (data == null || data.length == 0) {
- raf.setLength(0);
- } else {
- raf.write(data, 0, data.length);
- }
- raf.close();
- fsyncDirectory(path.getParentFile());
+ out = file.startWrite();
+ out.write(data);
+ file.finishWrite(out);
+ out = null;
} catch (IOException e) {
- Slog.e(TAG, "Error writing to file " + e);
+ Slog.e(TAG, "Error writing file " + path, e);
} finally {
- if (raf != null) {
- try {
- raf.close();
- } catch (IOException e) {
- Slog.e(TAG, "Error closing file " + e);
- }
- }
+ file.failWrite(out);
}
+ // For performance reasons, AtomicFile only syncs the file itself, not also the parent
+ // directory. The latter must be done explicitly here, as some callers need a guarantee
+ // that the file really exists on-disk when this returns.
+ fsyncDirectory(path.getParentFile());
mCache.putFile(path, data);
}
}
private void deleteFile(File path) {
synchronized (mFileWriteLock) {
+ // Zeroize the file to try to make its contents unrecoverable. This is *not* guaranteed
+ // to be effective, and in fact it usually isn't, but it doesn't hurt. We also don't
+ // bother zeroizing |path|.new, which may exist from an interrupted AtomicFile write.
if (path.exists()) {
- // Zeroize the file to try to make its contents unrecoverable. This is *not*
- // guaranteed to be effective, and in fact it usually isn't, but it doesn't hurt.
try (RandomAccessFile raf = new RandomAccessFile(path, "rws")) {
final int fileSize = (int) raf.length();
raf.write(new byte[fileSize]);
} catch (Exception e) {
Slog.w(TAG, "Failed to zeroize " + path, e);
}
- path.delete();
- mCache.putFile(path, null);
}
+ // To ensure that |path|.new is deleted if it exists, use AtomicFile.delete() here.
+ new AtomicFile(path).delete();
+ mCache.putFile(path, null);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index c67d54f..f5ec880 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -131,8 +131,6 @@
UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
}
}
-
- mEventLogger.enqueue(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
}
};
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index bb37e0e..3421eb7 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -60,6 +60,7 @@
import android.content.res.ApkAssets;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FabricatedOverlayInternal;
@@ -881,7 +882,7 @@
}
Slog.d(TAG, "commit failed: " + e.getMessage(), e);
throw new SecurityException("commit failed"
- + (DEBUG ? ": " + e.getMessage() : ""));
+ + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
}
} finally {
traceEnd(TRACE_TAG_RRO);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b302d4a..8089af3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5063,8 +5063,11 @@
"getUnsuspendablePackagesForUser");
final int callingUid = Binder.getCallingUid();
if (UserHandle.getUserId(callingUid) != userId) {
- throw new SecurityException("Calling uid " + callingUid
- + " cannot query getUnsuspendablePackagesForUser for user " + userId);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "Calling uid " + callingUid
+ + " cannot query getUnsuspendablePackagesForUser for user "
+ + userId);
}
return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(),
packageNames, userId, callingUid);
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index aeb11b7..fbfc84a 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -196,8 +196,11 @@
appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags,
true /* migrateAppData */);
}
- } catch (IllegalStateException e) {
- // Device was probably ejected, and we'll process that event momentarily
+ } catch (RuntimeException e) {
+ // The volume was probably already unmounted. We'll probably process the unmount
+ // event momentarily. TODO(b/256909937): ignoring errors from prepareUserStorage()
+ // is very dangerous. Instead, we should fix the race condition that allows this
+ // code to run on an unmounted volume in the first place.
Slog.w(TAG, "Failed to prepare storage: " + e);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d2af78b..f85b11d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -19,6 +19,7 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import android.Manifest;
import android.accounts.Account;
@@ -314,7 +315,7 @@
@VisibleForTesting
static class UserData {
// Basic user information and properties
- UserInfo info;
+ @NonNull UserInfo info;
// Account name used when there is a strong association between a user and an account
String account;
// Account information for seeding into a newly created user. This could also be
@@ -3268,11 +3269,39 @@
}
}
+ /** Checks whether the device is currently in headless system user mode (for any reason). */
+ @Override
+ public boolean isHeadlessSystemUserMode() {
+ synchronized (mUsersLock) {
+ final UserData systemUserData = mUsers.get(UserHandle.USER_SYSTEM);
+ return !systemUserData.info.isFull();
+ }
+ }
+
/**
- * Checks whether the device is really headless system user mode, ignoring system user mode
- * emulation.
+ * Checks whether the default state of the device is headless system user mode, i.e. what the
+ * mode would be if we did a fresh factory reset.
+ * If the mode is being emulated (via SYSTEM_USER_MODE_EMULATION_PROPERTY) then that will be
+ * returned instead.
+ * Note that, even in the absence of emulation, a device might deviate from the current default
+ * due to an OTA changing the default (which won't change the already-decided mode).
*/
- private boolean isReallyHeadlessSystemUserMode() {
+ private boolean isDefaultHeadlessSystemUserMode() {
+ if (!Build.isDebuggable()) {
+ return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
+ }
+
+ final String emulatedValue = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
+ if (!TextUtils.isEmpty(emulatedValue)) {
+ if (UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS.equals(emulatedValue)) return true;
+ if (UserManager.SYSTEM_USER_MODE_EMULATION_FULL.equals(emulatedValue)) return false;
+ if (!UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT.equals(emulatedValue)) {
+ Slogf.e(LOG_TAG, "isDefaultHeadlessSystemUserMode(): ignoring invalid valued of "
+ + "property %s: %s",
+ SYSTEM_USER_MODE_EMULATION_PROPERTY, emulatedValue);
+ }
+ }
+
return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
}
@@ -3284,30 +3313,11 @@
if (!Build.isDebuggable()) {
return;
}
-
- final String emulatedValue = SystemProperties
- .get(UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY);
- if (TextUtils.isEmpty(emulatedValue)) {
+ if (TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
return;
}
- final boolean newHeadlessSystemUserMode;
- switch (emulatedValue) {
- case UserManager.SYSTEM_USER_MODE_EMULATION_FULL:
- newHeadlessSystemUserMode = false;
- break;
- case UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS:
- newHeadlessSystemUserMode = true;
- break;
- case UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT:
- newHeadlessSystemUserMode = isReallyHeadlessSystemUserMode();
- break;
- default:
- Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): ignoring invalid valued of "
- + "property %s: %s", UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY,
- emulatedValue);
- return;
- }
+ final boolean newHeadlessSystemUserMode = isDefaultHeadlessSystemUserMode();
// Update system user type
synchronized (mPackagesLock) {
@@ -3343,7 +3353,7 @@
}
}
- // Update emulated mode, which will used to triger an update on user packages
+ // Update emulated mode, which will used to trigger an update on user packages
mUpdatingSystemUserMode = true;
}
@@ -3533,7 +3543,11 @@
synchronized (mUsersLock) {
UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
userData.info.flags |= UserInfo.FLAG_SYSTEM;
- if (!UserManager.isHeadlessSystemUserMode()) {
+ // We assume that isDefaultHeadlessSystemUserMode() does not change during the OTA
+ // from userVersion < 8 since it is documented that pre-R devices do not support its
+ // modification. Therefore, its current value should be the same as the pre-update
+ // version.
+ if (!isDefaultHeadlessSystemUserMode()) {
userData.info.flags |= UserInfo.FLAG_FULL;
}
userIdsToWrite.add(userData.info.id);
@@ -3742,7 +3756,7 @@
int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
| UserInfo.FLAG_PRIMARY;
// Create the system user
- String systemUserType = UserManager.isHeadlessSystemUserMode()
+ String systemUserType = isDefaultHeadlessSystemUserMode()
? UserManager.USER_TYPE_SYSTEM_HEADLESS
: UserManager.USER_TYPE_FULL_SYSTEM;
flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
@@ -6210,9 +6224,12 @@
com.android.internal.R.bool.config_guestUserEphemeral));
pw.println(" Force ephemeral users: " + mForceEphemeralUsers);
pw.println(" Is split-system user: " + UserManager.isSplitSystemUser());
- final boolean isHeadlessSystemUserMode = UserManager.isHeadlessSystemUserMode();
+ final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
pw.println(" Is headless-system mode: " + isHeadlessSystemUserMode);
- if (isHeadlessSystemUserMode != isReallyHeadlessSystemUserMode()) {
+ if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
+ pw.println(" (differs from the current default build value)");
+ }
+ if (!TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
pw.println(" (emulated by 'cmd user set-system-user-mode-emulation')");
if (mUpdatingSystemUserMode) {
pw.println(" (and being updated after boot)");
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b98d20e..5b6196f 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -128,7 +128,8 @@
.setIsCredentialSharableWithParent(true)
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
- .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT));
}
/**
@@ -163,7 +164,8 @@
.setIsCredentialSharableWithParent(true)
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
- .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE));
}
/**
diff --git a/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java b/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
index fa08add..227a3a1 100644
--- a/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
+++ b/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
@@ -39,6 +39,7 @@
// These need to be kept in sync with system/security/ondevice-signing/StatsReporter.{h, cpp}.
private static final String METRICS_FILE = "/data/misc/odsign/metrics/odsign-metrics.txt";
private static final String COMPOS_METRIC_NAME = "comp_os_artifacts_check_record";
+ private static final String ODSIGN_METRIC_NAME = "odsign_record";
/**
* Arrange for stats to be uploaded in the background.
@@ -64,18 +65,45 @@
for (String line : lines.split("\n")) {
String[] metrics = line.split(" ");
- if (metrics.length != 4 || !metrics[0].equals(COMPOS_METRIC_NAME)) {
- Slog.w(TAG, "Malformed metrics file");
- break;
+ if (line.isEmpty() || metrics.length < 1) {
+ Slog.w(TAG, "Empty metrics line");
+ continue;
}
- boolean currentArtifactsOk = metrics[1].equals("1");
- boolean compOsPendingArtifactsExists = metrics[2].equals("1");
- boolean useCompOsGeneratedArtifacts = metrics[3].equals("1");
+ switch (metrics[0]) {
+ case COMPOS_METRIC_NAME: {
+ if (metrics.length != 4) {
+ Slog.w(TAG, "Malformed CompOS metrics line '" + line + "'");
+ continue;
+ }
- ArtStatsLog.write(ArtStatsLog.EARLY_BOOT_COMP_OS_ARTIFACTS_CHECK_REPORTED,
- currentArtifactsOk, compOsPendingArtifactsExists,
- useCompOsGeneratedArtifacts);
+ boolean currentArtifactsOk = metrics[1].equals("1");
+ boolean compOsPendingArtifactsExists = metrics[2].equals("1");
+ boolean useCompOsGeneratedArtifacts = metrics[3].equals("1");
+
+ ArtStatsLog.write(ArtStatsLog.EARLY_BOOT_COMP_OS_ARTIFACTS_CHECK_REPORTED,
+ currentArtifactsOk, compOsPendingArtifactsExists,
+ useCompOsGeneratedArtifacts);
+ break;
+ }
+ case ODSIGN_METRIC_NAME: {
+ if (metrics.length != 2) {
+ Slog.w(TAG, "Malformed odsign metrics line '" + line + "'");
+ continue;
+ }
+
+ try {
+ int status = Integer.parseInt(metrics[1]);
+ ArtStatsLog.write(ArtStatsLog.ODSIGN_REPORTED, status);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Malformed odsign metrics line '" + line + "'");
+ }
+
+ break;
+ }
+ default:
+ Slog.w(TAG, "Malformed metrics line '" + line + "'");
+ }
}
} catch (FileNotFoundException e) {
// This is normal and probably means no new metrics have been generated.
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 8772de3..a7d4cea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.apex.ApexInfo;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.Attribution;
@@ -79,11 +78,8 @@
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.pkg.parsing.ParsingUtils;
-import libcore.util.EmptyArray;
-
import java.io.File;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -109,21 +105,9 @@
public static PackageInfo generate(AndroidPackage pkg, int[] gids,
@PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
- @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+ @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
- grantedPermissions, state, userId, null, pkgSetting);
- }
-
- /**
- * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
- * @deprecated Once ENABLE_FEATURE_SCAN_APEX is removed, this should also be removed.
- */
- @Deprecated
- @Nullable
- public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, long flags,
- @Nullable PackageStateInternal pkgSetting, @UserIdInt int userId) {
- return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
- PackageUserStateInternal.DEFAULT, userId, apexInfo, pkgSetting);
+ grantedPermissions, state, userId, pkgSetting);
}
/**
@@ -132,8 +116,7 @@
private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
@PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
- @UserIdInt int userId, @Nullable ApexInfo apexInfo,
- @Nullable PackageStateInternal pkgSetting) {
+ @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
pkgSetting);
if (applicationInfo == null) {
@@ -247,22 +230,6 @@
&= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
}
- if (apexInfo != null) {
- File apexFile = new File(apexInfo.modulePath);
-
- info.applicationInfo.sourceDir = apexFile.getPath();
- info.applicationInfo.publicSourceDir = apexFile.getPath();
- info.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
- info.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
- if (apexInfo.isFactory) {
- info.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- } else {
- info.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- }
- info.isApex = true;
- info.isActiveApex = apexInfo.isActive;
- }
-
final SigningDetails signingDetails = pkg.getSigningDetails();
// deprecated method of getting signing certificates
if ((flags & PackageManager.GET_SIGNATURES) != 0) {
@@ -294,7 +261,7 @@
info.coreApp = pkg.isCoreApp();
info.isApex = pkg.isApex();
- if (pkgSetting != null && !pkgSetting.hasSharedUser()) {
+ if (!pkgSetting.hasSharedUser()) {
// It is possible that this shared UID app has left
info.sharedUserId = null;
info.sharedUserLabel = 0;
@@ -452,7 +419,7 @@
public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
@PackageManager.ApplicationInfoFlagsBits long flags,
@NonNull PackageUserStateInternal state, @UserIdInt int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @NonNull PackageStateInternal pkgSetting) {
if (pkg == null) {
return null;
}
@@ -463,35 +430,31 @@
}
// Make shallow copy so we can store the metadata/libraries safely
- ApplicationInfo info = AndroidPackageUtils.toAppInfoWithoutState(pkg);
+ ApplicationInfo info = AndroidPackageUtils.generateAppInfoWithoutState(pkg);
updateApplicationInfo(info, flags, state);
initForUser(info, pkg, userId);
- if (pkgSetting != null) {
- // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
- PackageStateUnserialized pkgState = pkgSetting.getTransientState();
- info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
- List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
- var usesLibraries = pkgState.getUsesLibraryInfos();
- var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
- for (int index = 0; index < usesLibraries.size(); index++) {
- usesLibraryInfos.add(usesLibraries.get(index).getInfo());
- }
- info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
- ? null : usesLibraryFiles.toArray(new String[0]);
- info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
- if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
- info.category = pkgSetting.getCategoryOverride();
- }
+ // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
+ PackageStateUnserialized pkgState = pkgSetting.getTransientState();
+ info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
+ List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
+ var usesLibraries = pkgState.getUsesLibraryInfos();
+ var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
+ for (int index = 0; index < usesLibraries.size(); index++) {
+ usesLibraryInfos.add(usesLibraries.get(index).getInfo());
+ }
+ info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+ ? null : usesLibraryFiles.toArray(new String[0]);
+ info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+ if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+ info.category = pkgSetting.getCategoryOverride();
}
info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
- info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
- : pkgSetting.getPrimaryCpuAbi();
- info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
- : pkgSetting.getSecondaryCpuAbi();
+ info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+ info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
info.flags |= appInfoFlags(info.flags, pkgSetting);
info.privateFlags |= appInfoPrivateFlags(info.privateFlags, pkgSetting);
@@ -508,7 +471,7 @@
public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
@PackageManager.ComponentInfoFlagsBits long flags,
@NonNull PackageUserStateInternal state, @UserIdInt int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @NonNull PackageStateInternal pkgSetting) {
return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
}
@@ -520,7 +483,7 @@
public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
@PackageManager.ComponentInfoFlagsBits long flags,
@NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
- @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+ @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
if (a == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -597,7 +560,7 @@
@Nullable
public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
@PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
- @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+ @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
}
@@ -609,7 +572,7 @@
public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
@PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
@Nullable ApplicationInfo applicationInfo, int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @NonNull PackageStateInternal pkgSetting) {
if (s == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -647,7 +610,7 @@
public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
@PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
@NonNull ApplicationInfo applicationInfo, int userId,
- @Nullable PackageStateInternal pkgSetting) {
+ @NonNull PackageStateInternal pkgSetting) {
if (p == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -696,7 +659,7 @@
@Nullable
public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags,
- PackageUserStateInternal state, int userId, @Nullable PackageStateInternal pkgSetting) {
+ PackageUserStateInternal state, int userId, @NonNull PackageStateInternal pkgSetting) {
if (i == null) return null;
if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
return null;
@@ -719,10 +682,8 @@
initForUser(info, pkg, userId);
- info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
- : pkgSetting.getPrimaryCpuAbi();
- info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
- : pkgSetting.getSecondaryCpuAbi();
+ info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+ info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
info.nativeLibraryDir = pkg.getNativeLibraryDir();
info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
@@ -820,12 +781,11 @@
* all uninstalled and hidden packages as well.
*/
public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
- PackageStateInternal pkgSetting, PackageUserStateInternal state,
+ @NonNull PackageStateInternal pkgSetting, PackageUserStateInternal state,
@PackageManager.PackageInfoFlagsBits long flags) {
// Returns false if the package is hidden system app until installed.
if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
&& !state.isInstalled()
- && pkgSetting != null
&& pkgSetting.getTransientState().isHiddenUntilInstalled()) {
return false;
}
@@ -878,7 +838,7 @@
private static void assignFieldsComponentInfoParsedMainComponent(
@NonNull ComponentInfo info, @NonNull ParsedMainComponent component,
- @Nullable PackageStateInternal pkgSetting, int userId) {
+ @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
assignFieldsComponentInfoParsedMainComponent(info, component);
Pair<CharSequence, Integer> labelAndIcon =
ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -889,7 +849,7 @@
private static void assignFieldsPackageItemInfoParsedComponent(
@NonNull PackageItemInfo info, @NonNull ParsedComponent component,
- @Nullable PackageStateInternal pkgSetting, int userId) {
+ @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
assignFieldsPackageItemInfoParsedComponent(info, component);
Pair<CharSequence, Integer> labelAndIcon =
ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -1141,7 +1101,7 @@
@Nullable
public ApplicationInfo generate(AndroidPackage pkg,
@PackageManager.ApplicationInfoFlagsBits long flags, PackageUserStateInternal state,
- int userId, @Nullable PackageStateInternal pkgSetting) {
+ int userId, @NonNull PackageStateInternal pkgSetting) {
ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
if (appInfo != null) {
return appInfo;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index a6f1b29..5b0cc51 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -321,8 +321,4 @@
info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
}
-
- public static ApplicationInfo toAppInfoWithoutState(AndroidPackage pkg) {
- return ((ParsingPackageHidden) pkg).toAppInfoWithoutState();
- }
}
diff --git a/services/core/java/com/android/server/rollback/README.md b/services/core/java/com/android/server/rollback/README.md
index 0c5cc15..08800da 100644
--- a/services/core/java/com/android/server/rollback/README.md
+++ b/services/core/java/com/android/server/rollback/README.md
@@ -1,4 +1,4 @@
-#Rollback Manager
+# Rollback Manager
## Introduction
@@ -7,9 +7,9 @@
APEX update to the previous version installed on the device, and reverting any
APK or APEX data to the state it was in at the time of install.
-##Rollback Basics
+## Rollback Basics
-###How Rollbacks Work
+### How Rollbacks Work
A new install parameter ENABLE_ROLLBACK can be specified to enable rollback when
updating an application. For example:
@@ -42,27 +42,27 @@
See below for more details of shell commands for rollback.
-###Rollback Triggers
+### Rollback Triggers
-####Manually Triggered Rollback
+#### Manually Triggered Rollback
As mentioned above, it is possible to trigger rollback on device using a shell
command. This is for testing purposes only. We do not expect this mechanism to
be used in production in practice.
-####Watchdog Triggered Rollback
+#### Watchdog Triggered Rollback
Watchdog triggered rollback is intended to address severe issues with the
device. The platform provides several different watchdogs that can trigger
rollback.
-#####Package Watchdog
+##### Package Watchdog
There is a package watchdog service running on device that will trigger rollback
of an update if there are 5 ANRs or process crashes within a 1 minute window for
a package in the update.
-#####Native Watchdog
+##### Native Watchdog
If a native service crashes repeatedly after an update is installed, rollback
will be triggered. This particularly applies to updates that include APEXes
@@ -70,25 +70,25 @@
native services have been affected by an update, *any* crashing native service
will cause the rollback to be triggered.
-#####Explicit Health Check
+##### Explicit Health Check
There is an explicit check to verify the network stack is functional after an
update. If there is no network connectivity within a certain time period after
an update, rollback is triggered.
-####Server Triggered Rollback
+#### Server Triggered Rollback
The RollbackManager API may be used by the installer to roll back an update
based on a request from the server.
-##Rollback Details
+## Rollback Details
-###RollbackManager API
+### RollbackManager API
The RollbackManager API is an @SystemAPI guarded by the MANAGE_ROLLBACKS and
TEST_MANAGE_ROLLBACKS permissions. See RollbackManager.java for details about
the RollbackManager API.
-###Rollback of APEX modules
+### Rollback of APEX modules
Rollback is supported for APEX modules in addition to APK modules. In Q, there
was no concept of data associated with an APEX, so only the APEX itself is
@@ -100,7 +100,7 @@
directories). For example, FooV2.apex must not change the file format of some
state stored on the device in such a way that FooV1.apex cannot read the file.
-###Rollback of MultiPackage Installs
+### Rollback of MultiPackage Installs
Rollback can be enabled for multi-package installs. This requires that all
packages in the install session, including the parent session, have the
@@ -119,7 +119,7 @@
install session, rollback will not be enabled for any package in the
multi-package install session.
-###Rollback of Staged Installs
+### Rollback of Staged Installs
Rollback can be enabled for staged installs, which require reboot to take
effect. If reboot was required when the package was updated, then reboot is
@@ -127,21 +127,21 @@
package was updated, then no reboot is required when the package is rolled back.
-###Rollbacks on Multi User Devices
+### Rollbacks on Multi User Devices
Rollbacks should work properly on devices with multiple users. There is special
handling of user data backup to ensure app user data is properly backed up and
restored for all users, even for credential encrypted users that have not been
unlocked at various points during the flow.
-###Rollback whitelist
+### Rollback whitelist
Outside of testing, rollback may only be enabled for packages listed in the
sysconfig rollback whitelist - see
`SystemConfig#getRollbackWhitelistedPackages`. Attempts to enable rollback for
non-whitelisted packages will fail.
-###Failure to Enable Rollback
+### Failure to Enable Rollback
There are a number of reasons why we may be unable to enable rollback for a
package, including:
@@ -158,13 +158,13 @@
rollback enabled. Failing to enable rollback does not cause the installation to
fail.
-###Failure to Commit Rollback
+### Failure to Commit Rollback
For the most part, a rollback will remain available after failure to commit it.
This allows the caller to retry the rollback if they have reason to believe it
will not fail again the next time the commit of the rollback is attempted.
-###Installing Previously Rolled Back Packages
+### Installing Previously Rolled Back Packages
There is no logic in the platform itself to prevent installing a version of a
package that was previously rolled back.
@@ -175,7 +175,7 @@
installer to prevent reinstall of a previously rolled back package version if so
desired.
-###Rollback Expiration
+### Rollback Expiration
An available rollback is expired if the rollback lifetime has been exceeded or
if there is a new update to package associated with the rollback. When an
@@ -183,9 +183,9 @@
the rollback are deleted. Once a rollback is expired, it can no longer be
executed.
-##Shell Commands for Rollback
+## Shell Commands for Rollback
-###Installing an App with Rollback Enabled
+### Installing an App with Rollback Enabled
The `adb install` command accepts the `--enable-rollback` flag to install an app
with rollback enabled. For example:
@@ -194,7 +194,7 @@
$ adb install --enable-rollback FooV2.apk
```
-###Triggering Rollback Manually
+### Triggering Rollback Manually
If rollback is available for an application, the pm command can be used to
trigger rollback manually on device:
@@ -206,7 +206,7 @@
For rollback of staged installs, you have to manually reboot the device for the
rollback to take effect after running the 'pm rollback-app' command.
-###Listing the Status of Rollbacks on Device
+### Listing the Status of Rollbacks on Device
You can get a list with details about available and recently committed rollbacks
using dumpsys. For example:
@@ -246,9 +246,9 @@
The list of rollbacks is also included in bug reports. Search for "DUMP OF
SERVICE rollback".
-##Configuration Properties
+## Configuration Properties
-###Rollback Lifetime
+### Rollback Lifetime
Rollback lifetime refers to the maximum duration of time after the rollback is
first enabled that it will be available. The default is for rollbacks to be
@@ -263,7 +263,7 @@
The update will not take effect until after system server has been restarted.
-###Enable Rollback Timeout
+### Enable Rollback Timeout
The enable rollback timeout is how long RollbackManager is allowed to take to
enable rollback when performing an update. This includes the time needed to make
@@ -279,7 +279,7 @@
The update will take effect for the next install with rollback enabled.
-##Limitations
+## Limitations
* You cannot enable rollback for the first version of an application installed
on the device. Only updates to a package previously installed on the device can
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 61c21e6..ab35dc8 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -35,6 +35,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
+import static android.hardware.SensorPrivacyManager.EXTRA_TOGGLE_TYPE;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.hardware.SensorPrivacyManager.Sources.DIALOG;
@@ -664,6 +665,29 @@
.build());
}
+ private void showSensorStateChangedActivity(@SensorPrivacyManager.Sensors.Sensor int sensor,
+ @SensorPrivacyManager.ToggleType int toggleType) {
+ String activityName = mContext.getResources().getString(
+ R.string.config_sensorStateChangedActivity);
+ if (TextUtils.isEmpty(activityName)) {
+ return;
+ }
+
+ Intent dialogIntent = new Intent();
+ dialogIntent.setComponent(
+ ComponentName.unflattenFromString(activityName));
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setTaskOverlay(true, true);
+
+ dialogIntent.addFlags(
+ FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_NO_USER_ACTION);
+
+ dialogIntent.putExtra(EXTRA_SENSOR, sensor);
+ dialogIntent.putExtra(EXTRA_TOGGLE_TYPE, toggleType);
+ mContext.startActivityAsUser(dialogIntent, options.toBundle(), UserHandle.SYSTEM);
+ }
+
private boolean isTelevision(Context context) {
int uiMode = context.getResources().getConfiguration().uiMode;
return (uiMode & Configuration.UI_MODE_TYPE_MASK)
@@ -1378,6 +1402,8 @@
mToggleSensorListeners.finishBroadcast();
}
}
+
+ mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
}
public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 4111446..43ffa81 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -18,11 +18,11 @@
import android.annotation.Nullable;
import android.app.ITransientNotificationCallback;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
import android.os.IBinder;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -158,7 +158,7 @@
/** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
LetterboxDetails[] letterboxDetails);
/** @see com.android.internal.statusbar.IStatusBar#showTransient */
@@ -192,9 +192,10 @@
void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable);
/**
- * Sets the system-wide listener for UDFPS HBM status changes.
+ * Sets the system-wide callback for UDFPS refresh rate changes.
*
- * @see com.android.internal.statusbar.IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+ * @see com.android.internal.statusbar.IStatusBar#setUdfpsRefreshRateCallback
+ * (IUdfpsRefreshRateRequestCallback)
*/
- void setUdfpsHbmListener(IUdfpsHbmListener listener);
+ void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index c59b90f..7281a47 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -56,7 +56,7 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.net.Uri;
@@ -83,7 +83,8 @@
import android.util.Slog;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -175,7 +176,7 @@
private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
@GuardedBy("mLock")
- private IUdfpsHbmListener mUdfpsHbmListener;
+ private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
@GuardedBy("mLock")
private IBiometricContextListener mBiometricContextListener;
@@ -617,15 +618,15 @@
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes,
String packageName, LetterboxDetails[] letterboxDetails) {
getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
letterboxDetails);
if (mBar != null) {
try {
mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
letterboxDetails);
} catch (RemoteException ex) { }
}
@@ -694,13 +695,13 @@
}
@Override
- public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
synchronized (mLock) {
- mUdfpsHbmListener = listener;
+ mUdfpsRefreshRateRequestCallback = callback;
}
if (mBar != null) {
try {
- mBar.setUdfpsHbmListener(listener);
+ mBar.setUdfpsRefreshRateCallback(callback);
} catch (RemoteException ex) { }
}
}
@@ -944,11 +945,11 @@
}
@Override
- public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+ public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
enforceStatusBarService();
if (mBar != null) {
try {
- mBar.setUdfpsHbmListener(listener);
+ mBar.setUdfpsRefreshRateCallback(callback);
} catch (RemoteException ex) {
}
}
@@ -1211,7 +1212,7 @@
private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
private boolean mNavbarColorManagedByIme = false;
private @Behavior int mBehavior;
- private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
private String mPackageName = "none";
private int mDisabled1 = 0;
private int mDisabled2 = 0;
@@ -1223,14 +1224,14 @@
private void setBarAttributes(@Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ @Behavior int behavior, @InsetsType int requestedVisibleTypes,
String packageName,
LetterboxDetails[] letterboxDetails) {
mAppearance = appearance;
mAppearanceRegions = appearanceRegions;
mNavbarColorManagedByIme = navbarColorManagedByIme;
mBehavior = behavior;
- mRequestedVisibilities = requestedVisibilities;
+ mRequestedVisibleTypes = requestedVisibleTypes;
mPackageName = packageName;
mLetterboxDetails = letterboxDetails;
}
@@ -1363,7 +1364,7 @@
state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
state.mImeBackDisposition, state.mShowImeSwitcher,
gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
- state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities,
+ state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibleTypes,
state.mPackageName, transientBarTypes, state.mLetterboxDetails);
}
}
@@ -1374,11 +1375,11 @@
mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null);
});
// If StatusBarService dies, system_server doesn't get killed with it, so we need to make
- // sure the UDFPS listener is refreshed as well. Deferring to the handler just so to avoid
+ // sure the UDFPS callback is refreshed as well. Deferring to the handler just so to avoid
// making registerStatusBar re-entrant.
mHandler.post(() -> {
synchronized (mLock) {
- setUdfpsHbmListener(mUdfpsHbmListener);
+ setUdfpsRefreshRateCallback(mUdfpsRefreshRateRequestCallback);
setBiometicContextListener(mBiometricContextListener);
}
});
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4972412..8d106f7 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -28,7 +28,7 @@
import com.android.server.LocalServices;
import com.android.server.SystemClockTime;
import com.android.server.SystemClockTime.TimeConfidence;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
import java.io.PrintWriter;
import java.util.Objects;
@@ -60,10 +60,10 @@
@Override
public void setConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
- ConfigurationChangeListener configurationChangeListener =
+ @NonNull StateChangeListener listener) {
+ StateChangeListener stateChangeListener =
() -> mHandler.post(listener::onChange);
- mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
+ mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
}
@Override
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 773b517..e9827ce 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -25,8 +25,8 @@
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -185,8 +185,7 @@
* ensure O(1) lookup performance when working out whether a listener should trigger.
*/
@GuardedBy("mListeners")
- private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
- new ArrayMap<>();
+ private final ArrayMap<StateChangeListener, HashSet<String>> mListeners = new ArrayMap<>();
private static final Object SLOCK = new Object();
@@ -213,7 +212,7 @@
private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
synchronized (mListeners) {
- for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
+ for (Map.Entry<StateChangeListener, HashSet<String>> listenerEntry
: mListeners.entrySet()) {
// It's unclear which set of the following two Sets is going to be larger in the
// average case: monitoredKeys will be a subset of the set of possible keys, but
@@ -249,7 +248,7 @@
* <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no
* associated remove method.
*/
- public void addListener(@NonNull ConfigurationChangeListener listener,
+ public void addListener(@NonNull StateChangeListener listener,
@NonNull Set<String> keys) {
Objects.requireNonNull(listener);
Objects.requireNonNull(keys);
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index a39f64c..ff180eb 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -19,7 +19,7 @@
import android.annotation.UserIdInt;
import android.app.time.TimeConfiguration;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
/**
* An interface that provides access to service configuration for time detection. This hides
@@ -33,18 +33,18 @@
* Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
* The listener is invoked on the main thread.
*/
- void addConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+ void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Removes a listener previously added via {@link
- * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+ * #addConfigurationInternalChangeListener(StateChangeListener)}.
*/
- void removeConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+ void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
* snapshot so callers must use {@link
- * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+ * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
* changes.
*/
@NonNull
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 71acf35..4ef713c 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -49,7 +49,7 @@
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
import java.time.Instant;
import java.util.ArrayList;
@@ -104,8 +104,8 @@
@NonNull private final ServerFlagsOriginPrioritiesSupplier mServerFlagsOriginPrioritiesSupplier;
@GuardedBy("this")
- @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
- new ArrayList<>();
+ @NonNull
+ private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
/**
* If a newly calculated system clock time and the current system clock time differs by this or
@@ -167,20 +167,20 @@
}
private synchronized void handleConfigurationInternalChangeOnMainThread() {
- for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+ for (StateChangeListener changeListener : mConfigurationInternalListeners) {
changeListener.onChange();
}
}
@Override
public synchronized void addConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
}
@Override
public synchronized void removeConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 3cee19c..13ec753 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -44,8 +44,8 @@
import com.android.server.SystemClockTime;
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timezonedetector.ArrayMapWithHistory;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ReferenceWithHistory;
+import com.android.server.timezonedetector.StateChangeListener;
import java.io.PrintWriter;
import java.time.Duration;
@@ -136,11 +136,11 @@
public interface Environment {
/**
- * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
- * changes that could affect the content of {@link ConfigurationInternal}.
+ * Sets a {@link StateChangeListener} that will be invoked when there are any changes that
+ * could affect the content of {@link ConfigurationInternal}.
* This is invoked during system server setup.
*/
- void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+ void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/** Returns the {@link ConfigurationInternal} for the current user. */
@NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 8e2a5f4..0409a84 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -26,7 +26,6 @@
import android.annotation.UserIdInt;
import android.app.time.Capabilities.CapabilityState;
import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.os.UserHandle;
@@ -75,7 +74,7 @@
mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
- mUserId = builder.mUserId;
+ mUserId = Objects.requireNonNull(builder.mUserId, "userId must be set");
mUserConfigAllowed = builder.mUserConfigAllowed;
mLocationEnabledSetting = builder.mLocationEnabledSetting;
mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
@@ -151,8 +150,7 @@
* Returns true if the user is allowed to modify time zone configuration, e.g. can be false due
* to device policy (enterprise).
*
- * <p>See also {@link #createCapabilitiesAndConfig(boolean)} for situations where this value
- * are ignored.
+ * <p>See also {@link #asCapabilities(boolean)} for situations where this value is ignored.
*/
public boolean isUserConfigAllowed() {
return mUserConfigAllowed;
@@ -196,20 +194,8 @@
|| getGeoDetectionRunInBackgroundEnabled());
}
- /**
- * Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values.
- *
- * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
- * policy restrictions that should apply to actual users can be ignored
- */
- public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
- boolean bypassUserPolicyChecks) {
- return new TimeZoneCapabilitiesAndConfig(
- asCapabilities(bypassUserPolicyChecks), asConfiguration());
- }
-
@NonNull
- private TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
+ public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
UserHandle userHandle = UserHandle.of(mUserId);
TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
@@ -262,7 +248,7 @@
}
/** Returns a {@link TimeZoneConfiguration} from the configuration values. */
- private TimeZoneConfiguration asConfiguration() {
+ public TimeZoneConfiguration asConfiguration() {
return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
.setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
@@ -335,8 +321,7 @@
*/
public static class Builder {
- private final @UserIdInt int mUserId;
-
+ private @UserIdInt Integer mUserId;
private boolean mUserConfigAllowed;
private boolean mTelephonyDetectionSupported;
private boolean mGeoDetectionSupported;
@@ -348,11 +333,9 @@
private boolean mGeoDetectionEnabledSetting;
/**
- * Creates a new Builder with only the userId set.
+ * Creates a new Builder.
*/
- public Builder(@UserIdInt int userId) {
- mUserId = userId;
- }
+ public Builder() {}
/**
* Creates a new Builder by copying values from an existing instance.
@@ -371,6 +354,14 @@
}
/**
+ * Sets the user ID the configuration is for.
+ */
+ public Builder setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ return this;
+ }
+
+ /**
* Sets whether the user is allowed to configure time zone settings on this device.
*/
public Builder setUserConfigAllowed(boolean configAllowed) {
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 4749f73..5cb48c2 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -18,8 +18,6 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -29,7 +27,6 @@
import com.android.server.SystemTimeZone.TimeZoneConfidence;
import java.io.PrintWriter;
-import java.util.Objects;
/**
* The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
@@ -38,29 +35,7 @@
private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
- @NonNull private final Context mContext;
- @NonNull private final Handler mHandler;
- @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
-
- EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
- @NonNull ServiceConfigAccessor serviceConfigAccessor) {
- mContext = Objects.requireNonNull(context);
- mHandler = Objects.requireNonNull(handler);
- mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
- }
-
- @Override
- public void setConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
- ConfigurationChangeListener configurationChangeListener =
- () -> mHandler.post(listener::onChange);
- mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
- }
-
- @Override
- @NonNull
- public ConfigurationInternal getCurrentUserConfigurationInternal() {
- return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+ EnvironmentImpl() {
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 8da5d6a..4ac2ba5 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -59,20 +59,18 @@
* Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
* The listener is invoked on the main thread.
*/
- void addConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener);
+ void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Removes a listener previously added via {@link
- * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+ * #addConfigurationInternalChangeListener(StateChangeListener)}.
*/
- void removeConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener);
+ void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
/**
* Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
* snapshot so callers must use {@link
- * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+ * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
* changes.
*/
@NonNull
@@ -104,8 +102,7 @@
*
* <p>Note: Currently only for use by long-lived objects; there is no associated remove method.
*/
- void addLocationTimeZoneManagerConfigListener(
- @NonNull ConfigurationChangeListener listener);
+ void addLocationTimeZoneManagerConfigListener(@NonNull StateChangeListener listener);
/**
* Returns {@code true} if the telephony-based time zone detection feature is supported on the
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index e2f4246..dfb9619 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -22,7 +22,6 @@
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -104,8 +103,8 @@
@NonNull private final LocationManager mLocationManager;
@GuardedBy("this")
- @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
- new ArrayList<>();
+ @NonNull
+ private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
/**
* The mode to use for the primary location time zone provider in a test. Setting this
@@ -207,20 +206,20 @@
}
private synchronized void handleConfigurationInternalChangeOnMainThread() {
- for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+ for (StateChangeListener changeListener : mConfigurationInternalListeners) {
changeListener.onChange();
}
}
@Override
public synchronized void addConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
}
@Override
public synchronized void removeConfigurationInternalChangeListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
}
@@ -237,10 +236,10 @@
@NonNull TimeZoneConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) {
Objects.requireNonNull(requestedConfiguration);
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId)
- .createCapabilitiesAndConfig(bypassUserPolicyChecks);
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
- TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
+ ConfigurationInternal configurationInternal = getConfigurationInternal(userId);
+ TimeZoneCapabilities capabilities =
+ configurationInternal.asCapabilities(bypassUserPolicyChecks);
+ TimeZoneConfiguration oldConfiguration = configurationInternal.asConfiguration();
final TimeZoneConfiguration newConfiguration =
capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
@@ -292,7 +291,8 @@
@Override
@NonNull
public synchronized ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
- return new ConfigurationInternal.Builder(userId)
+ return new ConfigurationInternal.Builder()
+ .setUserId(userId)
.setTelephonyDetectionFeatureSupported(
isTelephonyTimeZoneDetectionFeatureSupported())
.setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
@@ -354,7 +354,7 @@
@Override
public void addLocationTimeZoneManagerConfigListener(
- @NonNull ConfigurationChangeListener listener) {
+ @NonNull StateChangeListener listener) {
mServerFlags.addListener(listener, LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH);
}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
similarity index 78%
rename from services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
rename to services/core/java/com/android/server/timezonedetector/StateChangeListener.java
index aa8ad37..2b5639c 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
+++ b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
@@ -17,11 +17,11 @@
package com.android.server.timezonedetector;
/**
- * A listener used to receive notification that configuration has / may have changed (depending on
+ * A listener used to receive notification that state has / may have changed (depending on
* the usecase).
*/
@FunctionalInterface
-public interface ConfigurationChangeListener {
- /** Called when the configuration may have changed. */
+public interface StateChangeListener {
+ /** Called when something (may have) changed. */
void onChange();
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index ce64eac..dfb44df 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -35,17 +35,14 @@
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final CurrentUserIdentityInjector mCurrentUserIdentityInjector;
- @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
@NonNull CurrentUserIdentityInjector currentUserIdentityInjector,
- @NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mCurrentUserIdentityInjector = Objects.requireNonNull(currentUserIdentityInjector);
- mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
}
@@ -53,10 +50,9 @@
@NonNull
public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfigForDpm() {
int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
- ConfigurationInternal configurationInternal =
- mServiceConfigAccessor.getConfigurationInternal(currentUserId);
final boolean bypassUserPolicyChecks = true;
- return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+ return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+ currentUserId, bypassUserPolicyChecks);
}
@Override
@@ -65,7 +61,7 @@
int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
final boolean bypassUserPolicyChecks = true;
- return mServiceConfigAccessor.updateConfiguration(
+ return mTimeZoneDetectorStrategy.updateConfiguration(
currentUserId, configuration, bypassUserPolicyChecks);
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 13f1694..f415cf0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -83,7 +83,7 @@
ServiceConfigAccessor serviceConfigAccessor =
ServiceConfigAccessorImpl.getInstance(context);
TimeZoneDetectorStrategy timeZoneDetectorStrategy =
- TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
+ TimeZoneDetectorStrategyImpl.create(handler, serviceConfigAccessor);
DeviceActivityMonitor deviceActivityMonitor =
DeviceActivityMonitorImpl.create(context, handler);
@@ -99,16 +99,14 @@
CurrentUserIdentityInjector currentUserIdentityInjector =
CurrentUserIdentityInjector.REAL;
TimeZoneDetectorInternal internal = new TimeZoneDetectorInternalImpl(
- context, handler, currentUserIdentityInjector, serviceConfigAccessor,
- timeZoneDetectorStrategy);
+ context, handler, currentUserIdentityInjector, timeZoneDetectorStrategy);
publishLocalService(TimeZoneDetectorInternal.class, internal);
// Publish the binder service so it can be accessed from other (appropriately
// permissioned) processes.
CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
TimeZoneDetectorService service = new TimeZoneDetectorService(
- context, handler, callerIdentityInjector, serviceConfigAccessor,
- timeZoneDetectorStrategy);
+ context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
// Dump the device activity monitor when the service is dumped.
service.addDumpable(deviceActivityMonitor);
@@ -127,9 +125,6 @@
private final CallerIdentityInjector mCallerIdentityInjector;
@NonNull
- private final ServiceConfigAccessor mServiceConfigAccessor;
-
- @NonNull
private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
/**
@@ -150,18 +145,16 @@
@VisibleForTesting
public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
@NonNull CallerIdentityInjector callerIdentityInjector,
- @NonNull ServiceConfigAccessor serviceConfigAccessor,
@NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
mContext = Objects.requireNonNull(context);
mHandler = Objects.requireNonNull(handler);
mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
- mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
// Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
- // the configuration changes for any reason.
- mServiceConfigAccessor.addConfigurationInternalChangeListener(
- () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread));
+ // the detector state changes for any reason.
+ mTimeZoneDetectorStrategy.addChangeListener(
+ () -> mHandler.post(this::handleChangeOnHandlerThread));
}
@Override
@@ -174,12 +167,15 @@
TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId) {
enforceManageTimeZoneDetectorPermission();
+ // Resolve constants like USER_CURRENT to the true user ID as needed.
+ int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, false, "getCapabilitiesAndConfig", null);
+
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- ConfigurationInternal configurationInternal =
- mServiceConfigAccessor.getConfigurationInternal(userId);
final boolean bypassUserPolicyChecks = false;
- return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+ return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+ resolvedUserId, bypassUserPolicyChecks);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
@@ -204,7 +200,7 @@
final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
final boolean bypassUserPolicyChecks = false;
- return mServiceConfigAccessor.updateConfiguration(
+ return mTimeZoneDetectorStrategy.updateConfiguration(
resolvedUserId, configuration, bypassUserPolicyChecks);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
@@ -285,8 +281,9 @@
}
}
- void handleConfigurationInternalChangedOnHandlerThread() {
- // Configuration has changed, but each user may have a different view of the configuration.
+ void handleChangeOnHandlerThread() {
+ // Detector state has changed. Each user may have a different view of the configuration so
+ // no information is passed; each client must query what they're interested in.
// It's possible that this will cause unnecessary notifications but that shouldn't be a
// problem.
synchronized (mListeners) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 69284e3..328cf72 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,6 +17,8 @@
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -94,6 +96,48 @@
*/
public interface TimeZoneDetectorStrategy extends Dumpable {
+ /**
+ * Adds a listener that will be triggered when something changes that could affect the result
+ * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This
+ * includes the current user changing. This is exposed so that (indirect) users like SettingsUI
+ * can monitor for changes to data derived from {@link TimeZoneCapabilitiesAndConfig} and update
+ * the UI accordingly.
+ */
+ void addChangeListener(StateChangeListener listener);
+
+ /**
+ * Returns a {@link TimeZoneCapabilitiesAndConfig} object for the specified user.
+ *
+ * <p>The strategy is dependent on device state like current user, settings and device config.
+ * These updates are usually handled asynchronously, so callers should expect some delay between
+ * a change being made directly to services like settings and the strategy becoming aware of
+ * them. Changes made via {@link #updateConfiguration} will be visible immediately.
+ *
+ * @param userId the user ID to retrieve the information for
+ * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+ * policy restrictions that should apply to actual users can be ignored
+ */
+ TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+ @UserIdInt int userId, boolean bypassUserPolicyChecks);
+
+ /**
+ * Updates the configuration properties that control a device's time zone behavior.
+ *
+ * <p>This method returns {@code true} if the configuration was changed, {@code false}
+ * otherwise.
+ *
+ * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to
+ * subsequent calls.
+ *
+ * @param userId the current user ID, supplied to make sure that the asynchronous process
+ * that happens when users switch is completed when the call is made
+ * @param configuration the configuration changes
+ * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+ * policy restrictions that should apply to actual users can be ignored
+ */
+ boolean updateConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration,
+ boolean bypassUserPolicyChecks);
+
/** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
@NonNull
TimeZoneState getTimeZoneState();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 18c8885..ecf25e9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -31,10 +31,10 @@
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.content.Context;
import android.os.Handler;
import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
@@ -46,6 +46,7 @@
import java.io.PrintWriter;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -68,16 +69,6 @@
public interface Environment {
/**
- * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
- * changes that could affect the content of {@link ConfigurationInternal}.
- * This is invoked during system server setup.
- */
- void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
-
- /** Returns the {@link ConfigurationInternal} for the current user. */
- @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
-
- /**
* Returns the device's currently configured time zone. May return an empty string.
*/
@NonNull String getDeviceTimeZone();
@@ -206,6 +197,22 @@
private final ReferenceWithHistory<ManualTimeZoneSuggestion> mLatestManualSuggestion =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+ @NonNull
+ private final ServiceConfigAccessor mServiceConfigAccessor;
+
+ /** The handler used for asynchronous operations triggered by this. */
+ @NonNull
+ private final Handler mStateChangeHandler;
+
+ @GuardedBy("this")
+ @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+
+ /**
+ * A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
+ * because it is relatively heavyweight to obtain and is used more often than it is expected to
+ * change. Because many operations are asynchronous, this value may be out of date but should
+ * be "eventually consistent".
+ */
@GuardedBy("this")
@NonNull
private ConfigurationInternal mCurrentConfigurationInternal;
@@ -229,29 +236,93 @@
* Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
*/
public static TimeZoneDetectorStrategyImpl create(
- @NonNull Context context, @NonNull Handler handler,
- @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+ @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) {
- Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor);
- return new TimeZoneDetectorStrategyImpl(environment);
+ Environment environment = new EnvironmentImpl();
+ return new TimeZoneDetectorStrategyImpl(serviceConfigAccessor, handler, environment);
}
@VisibleForTesting
- public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
+ public TimeZoneDetectorStrategyImpl(
+ @NonNull ServiceConfigAccessor serviceConfigAccessor,
+ @NonNull Handler handler, @NonNull Environment environment) {
mEnvironment = Objects.requireNonNull(environment);
+ mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
+ mStateChangeHandler = Objects.requireNonNull(handler);
// Start with telephony fallback enabled.
mTelephonyTimeZoneFallbackEnabled =
new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true);
synchronized (this) {
- mEnvironment.setConfigurationInternalChangeListener(
- this::handleConfigurationInternalChanged);
- mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal();
+ // Listen for config and user changes and get an initial snapshot of configuration.
+ StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
+ mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
+ mCurrentConfigurationInternal =
+ mServiceConfigAccessor.getCurrentUserConfigurationInternal();
}
}
@Override
+ public synchronized TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+ @UserIdInt int userId, boolean bypassUserPolicyChecks) {
+ ConfigurationInternal configurationInternal;
+ if (mCurrentConfigurationInternal.getUserId() == userId) {
+ // Use the cached snapshot we have.
+ configurationInternal = mCurrentConfigurationInternal;
+ } else {
+ // This is not a common case: It would be unusual to want the configuration for a user
+ // other than the "current" user, but it is supported because it is trivial to do so.
+ // Unlike the current user config, there's no cached copy to worry about so read it
+ // directly from mServiceConfigAccessor.
+ configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
+ }
+ return new TimeZoneCapabilitiesAndConfig(
+ configurationInternal.asCapabilities(bypassUserPolicyChecks),
+ configurationInternal.asConfiguration());
+ }
+
+ @Override
+ public synchronized boolean updateConfiguration(
+ @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration,
+ boolean bypassUserPolicyChecks) {
+
+ // Write-through
+ boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration(
+ userId, configuration, bypassUserPolicyChecks);
+
+ // The update above will trigger config update listeners asynchronously if they are needed,
+ // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
+ // wouldn't see the update. So, handle the cache update and notifications here. When the
+ // async update listener triggers it will find everything already up to date and do nothing.
+ if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
+ ConfigurationInternal configurationInternal =
+ mServiceConfigAccessor.getConfigurationInternal(userId);
+
+ // If the configuration actually changed, update the cached copy synchronously and do
+ // other necessary house-keeping / (async) listener notifications.
+ if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
+ mCurrentConfigurationInternal = configurationInternal;
+
+ String logMsg = "updateConfiguration:"
+ + " userId=" + userId
+ + ", configuration=" + configuration
+ + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
+ + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
+ logTimeZoneDebugInfo(logMsg);
+
+ handleConfigurationInternalChanged(logMsg);
+ }
+ }
+ return updateSuccessful;
+ }
+
+ @Override
+ public synchronized void addChangeListener(StateChangeListener listener) {
+ mStateChangeListeners.add(listener);
+ }
+
+ @Override
public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
Objects.requireNonNull(timeZoneId);
@@ -334,9 +405,8 @@
String timeZoneId = suggestion.getZoneId();
String cause = "Manual time suggestion received: suggestion=" + suggestion;
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities =
+ currentUserConfig.asCapabilities(bypassUserPolicyChecks);
if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
+ ": capabilities=" + capabilities
@@ -735,18 +805,37 @@
return findBestTelephonySuggestion();
}
- private synchronized void handleConfigurationInternalChanged() {
+ /**
+ * Handles a configuration change notification.
+ */
+ private synchronized void handleConfigurationInternalMaybeChanged() {
ConfigurationInternal currentUserConfig =
- mEnvironment.getCurrentUserConfigurationInternal();
- String logMsg = "handleConfigurationInternalChanged:"
- + " oldConfiguration=" + mCurrentConfigurationInternal
- + ", newConfiguration=" + currentUserConfig;
- logTimeZoneDebugInfo(logMsg);
- mCurrentConfigurationInternal = currentUserConfig;
+ mServiceConfigAccessor.getCurrentUserConfigurationInternal();
- // The configuration change may have changed available suggestions or the way suggestions
- // are used, so re-run detection.
- doAutoTimeZoneDetection(currentUserConfig, logMsg);
+ // The configuration may not actually have changed so check before doing anything.
+ if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
+ String logMsg = "handleConfigurationInternalMaybeChanged:"
+ + " oldConfiguration=" + mCurrentConfigurationInternal
+ + ", newConfiguration=" + currentUserConfig;
+ logTimeZoneDebugInfo(logMsg);
+
+ mCurrentConfigurationInternal = currentUserConfig;
+
+ handleConfigurationInternalChanged(logMsg);
+ }
+ }
+
+ /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
+ @GuardedBy("this")
+ private void handleConfigurationInternalChanged(@NonNull String logMsg) {
+ // Notify change listeners asynchronously.
+ for (StateChangeListener listener : mStateChangeListeners) {
+ mStateChangeHandler.post(listener::onChange);
+ }
+
+ // The configuration change may have changed available suggestions or the way
+ // suggestions are used, so re-run detection.
+ doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
}
/**
@@ -760,8 +849,7 @@
ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
final boolean bypassUserPolicyChecks = false;
ipw.println("[Capabilities="
- + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks)
- + "]");
+ + mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
ipw.println("mEnvironment.getDeviceTimeZoneConfidence()="
+ mEnvironment.getDeviceTimeZoneConfidence());
@@ -824,6 +912,11 @@
return mTelephonyTimeZoneFallbackEnabled.getValue();
}
+ @VisibleForTesting
+ public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
+ return mCurrentConfigurationInternal;
+ }
+
/**
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
index e7d16c8..5eeafc1 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
@@ -20,9 +20,9 @@
import android.annotation.NonNull;
import android.os.SystemClock;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
import java.time.Duration;
import java.util.Objects;
@@ -35,7 +35,7 @@
extends LocationTimeZoneProviderController.Environment {
@NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
- @NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener;
+ @NonNull private final StateChangeListener mConfigurationInternalChangeListener;
LocationTimeZoneProviderControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
@NonNull ServiceConfigAccessor serviceConfigAccessor,
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 6012993..d944a3b 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -655,7 +655,8 @@
}
private void registerSettingsChangeReceiver(IntentFilter intentFilter) {
- mContext.registerReceiver(mSettingChangeReceiver, intentFilter);
+ mContext.registerReceiver(mSettingChangeReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
@Nullable
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 44b83096..4fef2a8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -2225,8 +2225,7 @@
ProtoOutputStream proto = new ProtoOutputStream();
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
long timeOffsetNs =
- TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(),
- TimeUnit.NANOSECONDS)
+ TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- SystemClock.elapsedRealtimeNanos();
proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
mBuffer.writeTraceToFile(mTraceFile, proto);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 17a9a63..0eee8a3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -496,7 +496,7 @@
/** The most recently given options. */
private ActivityOptions mPendingOptions;
/** Non-null if {@link #mPendingOptions} specifies the remote animation. */
- private RemoteAnimationAdapter mPendingRemoteAnimation;
+ RemoteAnimationAdapter mPendingRemoteAnimation;
private RemoteTransition mPendingRemoteTransition;
ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
@@ -812,7 +812,6 @@
StartingData mStartingData;
WindowState mStartingWindow;
StartingSurfaceController.StartingSurface mStartingSurface;
- boolean startingDisplayed;
boolean startingMoved;
/** The last set {@link DropInputMode} for this activity surface. */
@@ -821,13 +820,6 @@
/** Whether the input to this activity will be dropped during the current playing animation. */
private boolean mIsInputDroppedForAnimation;
- /**
- * If it is non-null, it requires all activities who have the same starting data to be drawn
- * to remove the starting window.
- * TODO(b/189385912): Remove starting window related fields after migrating them to task.
- */
- private StartingData mSharedStartingData;
-
boolean mHandleExitSplashScreen;
@TransferSplashScreenState
int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -1201,14 +1193,11 @@
pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
pw.print(" mIsExiting="); pw.println(mIsExiting);
}
- if (mSharedStartingData != null) {
- pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
- }
- if (mStartingWindow != null || mStartingSurface != null
- || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
+ if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
+ || startingMoved || mVisibleSetFromTransferredStartingWindow) {
pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
pw.print(" startingSurface="); pw.print(mStartingSurface);
- pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+ pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed());
pw.print(" startingMoved="); pw.print(startingMoved);
pw.println(" mVisibleSetFromTransferredStartingWindow="
+ mVisibleSetFromTransferredStartingWindow);
@@ -2687,13 +2676,23 @@
}
}
+ boolean isStartingWindowDisplayed() {
+ final StartingData data = mStartingData != null ? mStartingData : task != null
+ ? task.mSharedStartingData : null;
+ return data != null && data.mIsDisplayed;
+ }
+
/** Called when the starting window is added to this activity. */
void attachStartingWindow(@NonNull WindowState startingWindow) {
startingWindow.mStartingData = mStartingData;
mStartingWindow = startingWindow;
- // The snapshot type may have called associateStartingDataWithTask().
- if (mStartingData != null && mStartingData.mAssociatedTask != null) {
- attachStartingSurfaceToAssociatedTask();
+ if (mStartingData != null) {
+ if (mStartingData.mAssociatedTask != null) {
+ // The snapshot type may have called associateStartingDataWithTask().
+ attachStartingSurfaceToAssociatedTask();
+ } else if (isEmbedded()) {
+ associateStartingWindowWithTaskIfNeeded();
+ }
}
}
@@ -2708,11 +2707,7 @@
/** Called when the starting window is not added yet but its data is known to fill the task. */
private void associateStartingDataWithTask() {
mStartingData.mAssociatedTask = task;
- task.forAllActivities(r -> {
- if (r.mVisibleRequested && !r.firstWindowDrawn) {
- r.mSharedStartingData = mStartingData;
- }
- });
+ task.mSharedStartingData = mStartingData;
}
/** Associates and attaches an added starting window to the current task. */
@@ -2743,10 +2738,8 @@
void removeStartingWindowAnimation(boolean prepareAnimation) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
- if (mSharedStartingData != null) {
- mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
- r.mSharedStartingData = null;
- });
+ if (task != null) {
+ task.mSharedStartingData = null;
}
if (mStartingWindow == null) {
if (mStartingData != null) {
@@ -2774,7 +2767,6 @@
mStartingData = null;
mStartingSurface = null;
mStartingWindow = null;
- startingDisplayed = false;
if (surface == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
+ "startingSurface==null, couldn't remove");
@@ -4247,7 +4239,7 @@
* @return {@code true} if starting window is in app's hierarchy.
*/
boolean hasStartingWindow() {
- if (startingDisplayed || mStartingData != null) {
+ if (mStartingData != null) {
return true;
}
for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -4345,10 +4337,7 @@
// Transfer the starting window over to the new token.
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
mStartingSurface = fromActivity.mStartingSurface;
- startingDisplayed = fromActivity.startingDisplayed;
- fromActivity.startingDisplayed = false;
mStartingWindow = tStartingWindow;
reportedVisible = fromActivity.reportedVisible;
fromActivity.mStartingData = null;
@@ -4414,7 +4403,6 @@
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Moving pending starting from %s to %s", fromActivity, this);
mStartingData = fromActivity.mStartingData;
- mSharedStartingData = fromActivity.mSharedStartingData;
fromActivity.mStartingData = null;
fromActivity.startingMoved = true;
scheduleAddStartingWindow();
@@ -6526,14 +6514,11 @@
// Remove starting window directly if is in a pure task. Otherwise if it is associated with
// a task (e.g. nested task fragment), then remove only if all visible windows in the task
// are drawn.
- final Task associatedTask =
- mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+ final Task associatedTask = task.mSharedStartingData != null ? task : null;
if (associatedTask == null) {
removeStartingWindow();
- } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
- // Don't block starting window removal if an Activity can't be a starting window
- // target.
- && r.mSharedStartingData != null) == null) {
+ } else if (associatedTask.getActivity(
+ r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
// The last drawn activity may not be the one that owns the starting window.
final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
if (r != null) {
@@ -6748,7 +6733,6 @@
if (mLastTransactionSequence != mWmService.mTransactionSequence) {
mLastTransactionSequence = mWmService.mTransactionSequence;
mNumDrawnWindows = 0;
- startingDisplayed = false;
// There is the main base application window, even if it is exiting, wait for it
mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6792,9 +6776,9 @@
isInterestingAndDrawn = true;
}
}
- } else if (w.isDrawn()) {
+ } else if (mStartingData != null && w.isDrawn()) {
// The starting window for this container is drawn.
- startingDisplayed = true;
+ mStartingData.mIsDisplayed = true;
}
}
@@ -7552,7 +7536,8 @@
ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
+ ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
- this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
+ this, reportedVisible, okToDisplay(), okToAnimate(),
+ isStartingWindowDisplayed());
// clean up thumbnail window
if (mThumbnail != null) {
@@ -9650,7 +9635,7 @@
if (mStartingWindow != null) {
mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW);
}
- proto.write(STARTING_DISPLAYED, startingDisplayed);
+ proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed());
proto.write(STARTING_MOVED, startingMoved);
proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
mVisibleSetFromTransferredStartingWindow);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1d70146..f070064 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2643,10 +2643,14 @@
}
}
- // Update the target's launch cookie to those specified in the options if set
+ // Update the target's launch cookie and pending remote animation to those specified in the
+ // options if set.
if (mStartActivity.mLaunchCookie != null) {
intentActivity.mLaunchCookie = mStartActivity.mLaunchCookie;
}
+ if (mStartActivity.mPendingRemoteAnimation != null) {
+ intentActivity.mPendingRemoteAnimation = mStartActivity.mPendingRemoteAnimation;
+ }
// Need to update mTargetRootTask because if task was moved out of it, the original root
// task may be destroyed.
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index d42a74f..7c0d658 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -40,6 +40,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.OptionalInt;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -332,9 +333,10 @@
String criticalEvents =
CriticalEventLog.getInstance().logLinesForSystemServerTraceFile();
final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
- null /* processCpuTracker */, null /* lastPids */, nativePids,
+ null /* processCpuTracker */, null /* lastPids */,
+ CompletableFuture.completedFuture(nativePids),
null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents,
- null/* AnrLatencyTracker */);
+ Runnable::run, null/* AnrLatencyTracker */);
if (tracesFile != null) {
tracesFile.renameTo(
new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index c2e87e6..9011533 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -76,7 +76,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.IntDef;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Trace;
@@ -167,16 +166,6 @@
? null : wallpaperTarget;
}
- @NonNull
- private static ArraySet<ActivityRecord> getAppsForAnimation(
- @NonNull ArraySet<ActivityRecord> apps, boolean excludeLauncherFromAnimation) {
- final ArraySet<ActivityRecord> appsForAnimation = new ArraySet<>(apps);
- if (excludeLauncherFromAnimation) {
- appsForAnimation.removeIf(ConfigurationContainer::isActivityTypeHome);
- }
- return appsForAnimation;
- }
-
/**
* Handle application transition for given display.
*/
@@ -226,45 +215,32 @@
mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded(
mDisplayContent.mOpeningApps);
- // Remove launcher from app transition animation while recents is running. Recents animation
- // is managed outside of app transition framework, so we just need to commit visibility.
- final boolean excludeLauncherFromAnimation =
- mDisplayContent.mOpeningApps.stream().anyMatch(
- (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS))
- || mDisplayContent.mClosingApps.stream().anyMatch(
- (app) -> app.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS));
- final ArraySet<ActivityRecord> openingAppsForAnimation = getAppsForAnimation(
- mDisplayContent.mOpeningApps, excludeLauncherFromAnimation);
- final ArraySet<ActivityRecord> closingAppsForAnimation = getAppsForAnimation(
- mDisplayContent.mClosingApps, excludeLauncherFromAnimation);
-
@TransitionOldType final int transit = getTransitCompatType(
- mDisplayContent.mAppTransition, openingAppsForAnimation, closingAppsForAnimation,
- mDisplayContent.mChangingContainers,
+ mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps,
+ mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers,
mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
mDisplayContent.mSkipAppTransitionAnimation);
mDisplayContent.mSkipAppTransitionAnimation = false;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"handleAppTransitionReady: displayId=%d appTransition={%s}"
- + " excludeLauncherFromAnimation=%b openingApps=[%s] closingApps=[%s] transit=%s",
- mDisplayContent.mDisplayId, appTransition.toString(), excludeLauncherFromAnimation,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- AppTransition.appTransitionOldToString(transit));
+ + " openingApps=[%s] closingApps=[%s] transit=%s",
+ mDisplayContent.mDisplayId, appTransition.toString(), mDisplayContent.mOpeningApps,
+ mDisplayContent.mClosingApps, AppTransition.appTransitionOldToString(transit));
// Find the layout params of the top-most application window in the tokens, which is
// what will control the animation theme. If all closing windows are obscured, then there is
// no need to do an animation. This is the case, for example, when this transition is being
// done behind a dream window.
- final ArraySet<Integer> activityTypes = collectActivityTypes(openingAppsForAnimation,
- closingAppsForAnimation, mDisplayContent.mChangingContainers);
+ final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
+ mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers);
final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,
- openingAppsForAnimation, closingAppsForAnimation,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
mDisplayContent.mChangingContainers);
final ActivityRecord topOpeningApp =
- getTopApp(openingAppsForAnimation, false /* ignoreHidden */);
+ getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
final ActivityRecord topClosingApp =
- getTopApp(closingAppsForAnimation, false /* ignoreHidden */);
+ getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);
final ActivityRecord topChangingApp =
getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
@@ -276,14 +252,14 @@
overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
}
- final boolean voiceInteraction = containsVoiceInteraction(closingAppsForAnimation)
- || containsVoiceInteraction(openingAppsForAnimation);
+ final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mClosingApps)
+ || containsVoiceInteraction(mDisplayContent.mOpeningApps);
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
try {
- applyAnimations(openingAppsForAnimation, closingAppsForAnimation, transit, animLp,
- voiceInteraction);
+ applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
+ animLp, voiceInteraction);
handleClosingApps();
handleOpeningApps();
handleChangingApps(transit);
@@ -294,8 +270,8 @@
final int flags = appTransition.getTransitFlags();
layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
appTransition.postAnimationCallback();
- appTransition.clear();
} finally {
+ appTransition.clear();
mService.mSurfaceAnimationRunner.continueStartingAnimations();
}
@@ -1200,14 +1176,19 @@
if (activity == null) {
continue;
}
+ if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Delaying app transition for recents animation to finish");
+ return false;
+ }
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
- activity, activity.allDrawn, activity.startingDisplayed,
+ activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
activity.startingMoved, activity.isRelaunching(),
activity.mStartingWindow);
final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
- if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
+ if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
return false;
}
if (allDrawn) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1cb83f12..d1d39af 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -243,16 +243,22 @@
} else if (currentActivity.isRootOfTask()) {
// TODO(208789724): Create single source of truth for this, maybe in
// RootWindowContainer
- // TODO: Also check Task.shouldUpRecreateTaskLocked() for prevActivity logic
prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask);
removedWindowContainer = currentTask;
- prevActivity = prevTask.getTopNonFinishingActivity();
- if (prevTask.isActivityTypeHome()) {
- backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+ // If it reaches the top activity, we will check the below task from parent.
+ // If it's null or multi-window, fallback the type to TYPE_CALLBACK.
+ // or set the type to proper value when it's return to home or another task.
+ if (prevTask == null || prevTask.inMultiWindowMode()) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
} else {
- backType = BackNavigationInfo.TYPE_CROSS_TASK;
+ prevActivity = prevTask.getTopNonFinishingActivity();
+ if (prevTask.isActivityTypeHome()) {
+ backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+ mShowWallpaper = true;
+ } else {
+ backType = BackNavigationInfo.TYPE_CROSS_TASK;
+ }
}
- mShowWallpaper = true;
}
infoBuilder.setType(backType);
@@ -263,8 +269,10 @@
removedWindowContainer,
BackNavigationInfo.typeToString(backType));
- // For now, we only animate when going home.
- boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ // For now, we only animate when going home and cross task.
+ boolean prepareAnimation =
+ (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ || backType == BackNavigationInfo.TYPE_CROSS_TASK)
&& adapter != null;
// Only prepare animation if no leash has been created (no animation is running).
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index e7ab63e..13a1cb6 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -216,14 +216,10 @@
return;
}
- if (container != null) {
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- } else {
- t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
- }
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
+ t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
t.setAlpha(d.mDimLayer, alpha);
t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
@@ -231,32 +227,6 @@
}
/**
- * Finish a dim started by dimAbove in the case there was no call to dimAbove.
- *
- * @param t A Transaction in which to finish the dim.
- */
- void stopDim(SurfaceControl.Transaction t) {
- if (mDimState != null) {
- t.hide(mDimState.mDimLayer);
- mDimState.isVisible = false;
- mDimState.mDontReset = false;
- }
- }
-
- /**
- * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
- * remove this effect. If the Dim can be assosciated with a particular child of the host
- * consider using the other variant of dimAbove which ties the Dim lifetime to the child
- * lifetime more explicitly.
- *
- * @param t A transaction in which to apply the Dim.
- * @param alpha The alpha at which to Dim.
- */
- void dimAbove(SurfaceControl.Transaction t, float alpha) {
- dim(t, null, 1, alpha, 0);
- }
-
- /**
* Place a dim above the given container, which should be a child of the host container.
* for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
* and the child should call dimAbove again to request the Dim to continue.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 38931f2..7bcea36 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -115,11 +115,9 @@
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
-import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowLayout;
@@ -314,7 +312,6 @@
private int mLastAppearance;
private int mLastBehavior;
private int mLastRequestedVisibleTypes = Type.defaultVisible();
- private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private AppearanceRegion[] mLastStatusBarAppearanceRegions;
private LetterboxDetails[] mLastLetterboxDetails;
@@ -2158,35 +2155,16 @@
mService.mInputManager.setSystemUiLightsOut(
isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
}
- final InsetsVisibilities requestedVisibilities =
- mLastRequestedVisibleTypes == requestedVisibleTypes
- ? mRequestedVisibilities
- : toInsetsVisibilities(requestedVisibleTypes);
mLastAppearance = appearance;
mLastBehavior = behavior;
mLastRequestedVisibleTypes = requestedVisibleTypes;
- mRequestedVisibilities = requestedVisibilities;
mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
mLastLetterboxDetails = letterboxDetails;
callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
- requestedVisibilities, focusedApp, letterboxDetails));
- }
-
- // TODO (253420890): Remove this when removing mRequestedVisibilities.
- private static InsetsVisibilities toInsetsVisibilities(@InsetsType int requestedVisibleTypes) {
- final @InsetsType int defaultVisibleTypes = WindowInsets.Type.defaultVisible();
- final InsetsVisibilities insetsVisibilities = new InsetsVisibilities();
- for (@InternalInsetsType int i = InsetsState.SIZE - 1; i >= 0; i--) {
- @InsetsType int type = InsetsState.toPublicType(i);
- if ((type & (requestedVisibleTypes ^ defaultVisibleTypes)) != 0) {
- // We only set the visibility if it is different from the default one.
- insetsVisibilities.setVisibility(i, (type & requestedVisibleTypes) != 0);
- }
- }
- return insetsVisibilities;
+ requestedVisibleTypes, focusedApp, letterboxDetails));
}
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index f11c2a7..dcb7fe3 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -604,7 +604,10 @@
getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
}
if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(false /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(false /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(mLockTaskModeState);
} catch (RemoteException ex) {
@@ -619,7 +622,10 @@
void showLockTaskToast() {
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
try {
- getStatusBarService().showPinningEscapeToast();
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEscapeToast();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to send pinning escape toast", e);
}
@@ -727,7 +733,10 @@
// When lock task starts, we disable the status bars.
try {
if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(true /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(true /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(lockTaskModeState);
mLockTaskModeState = lockTaskModeState;
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 9c85bc0..1cc1a57 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -20,13 +20,12 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import android.app.ActivityManager.RunningTaskInfo;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
-import java.util.Comparator;
-import java.util.Iterator;
+import java.util.ArrayList;
import java.util.List;
-import java.util.TreeSet;
import java.util.function.Consumer;
/**
@@ -39,15 +38,13 @@
static final int FLAG_CROSS_USERS = 1 << 2;
static final int FLAG_KEEP_INTENT_EXTRA = 1 << 3;
- // Comparator to sort by last active time (descending)
- private static final Comparator<Task> LAST_ACTIVE_TIME_COMPARATOR =
- (o1, o2) -> {
- return o1.lastActiveTime == o2.lastActiveTime
- ? Integer.signum(o2.mTaskId - o1.mTaskId) :
- Long.signum(o2.lastActiveTime - o1.lastActiveTime);
- };
-
- private final TreeSet<Task> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
+ // Tasks are sorted in order {focusedVisibleTasks, visibleTasks, invisibleTasks}.
+ private final ArrayList<Task> mTmpSortedTasks = new ArrayList<>();
+ // mTmpVisibleTasks, mTmpInvisibleTasks and mTmpFocusedTasks are sorted from top
+ // to bottom.
+ private final ArrayList<Task> mTmpVisibleTasks = new ArrayList<>();
+ private final ArrayList<Task> mTmpInvisibleTasks = new ArrayList<>();
+ private final ArrayList<Task> mTmpFocusedTasks = new ArrayList<>();
private int mCallingUid;
private int mUserId;
@@ -65,8 +62,6 @@
return;
}
- // Gather all of the tasks across all of the tasks, and add them to the sorted set
- mTmpSortedSet.clear();
mCallingUid = callingUid;
mUserId = UserHandle.getUserId(callingUid);
mCrossUser = (flags & FLAG_CROSS_USERS) == FLAG_CROSS_USERS;
@@ -77,19 +72,64 @@
mRecentTasks = recentTasks;
mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA;
- root.forAllLeafTasks(this, false /* traverseTopToBottom */);
+ if (root instanceof RootWindowContainer) {
+ ((RootWindowContainer) root).forAllDisplays(dc -> {
+ final Task focusedTask = dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null;
+ if (focusedTask != null) {
+ mTmpFocusedTasks.add(focusedTask);
+ }
+ processTaskInWindowContainer(dc);
+ });
+ } else {
+ final DisplayContent dc = root.getDisplayContent();
+ final Task focusedTask = dc != null
+ ? (dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null)
+ : null;
+ // May not be include focusedTask if root is DisplayArea.
+ final boolean rootContainsFocusedTask = focusedTask != null
+ && focusedTask.isDescendantOf(root);
+ if (rootContainsFocusedTask) {
+ mTmpFocusedTasks.add(focusedTask);
+ }
+ processTaskInWindowContainer(root);
+ }
+
+ final int visibleTaskCount = mTmpVisibleTasks.size();
+ for (int i = 0; i < mTmpFocusedTasks.size(); i++) {
+ final Task focusedTask = mTmpFocusedTasks.get(i);
+ final boolean containsFocusedTask = mTmpVisibleTasks.remove(focusedTask);
+ if (containsFocusedTask) {
+ // Put the visible focused task at the first position.
+ mTmpSortedTasks.add(focusedTask);
+ }
+ }
+ if (!mTmpVisibleTasks.isEmpty()) {
+ mTmpSortedTasks.addAll(mTmpVisibleTasks);
+ }
+ if (!mTmpInvisibleTasks.isEmpty()) {
+ mTmpSortedTasks.addAll(mTmpInvisibleTasks);
+ }
// Take the first {@param maxNum} tasks and create running task infos for them
- final Iterator<Task> iter = mTmpSortedSet.iterator();
- while (iter.hasNext()) {
- if (maxNum == 0) {
- break;
- }
-
- final Task task = iter.next();
- list.add(createRunningTaskInfo(task));
- maxNum--;
+ final int size = Math.min(maxNum, mTmpSortedTasks.size());
+ final long now = SystemClock.elapsedRealtime();
+ for (int i = 0; i < size; i++) {
+ final Task task = mTmpSortedTasks.get(i);
+ // Override the last active to current time for the visible tasks because the visible
+ // tasks can be considered to be currently active, the values are descending as
+ // the item order.
+ final long visibleActiveTime = i < visibleTaskCount ? now + size - i : -1;
+ list.add(createRunningTaskInfo(task, visibleActiveTime));
}
+
+ mTmpFocusedTasks.clear();
+ mTmpVisibleTasks.clear();
+ mTmpInvisibleTasks.clear();
+ mTmpSortedTasks.clear();
+ }
+
+ private void processTaskInWindowContainer(WindowContainer wc) {
+ wc.forAllLeafTasks(this, true /* traverseTopToBottom */);
}
@Override
@@ -117,25 +157,20 @@
// home & recent tasks
return;
}
-
if (task.isVisible()) {
- // For the visible task, update the last active time so that it can be used to determine
- // the order of the tasks (it may not be set for newly created tasks)
- task.touchActiveTime();
- if (!task.isFocused()) {
- // TreeSet doesn't allow the same value and make sure this task is lower than the
- // focused one.
- task.lastActiveTime -= mTmpSortedSet.size();
- }
+ mTmpVisibleTasks.add(task);
+ } else {
+ mTmpInvisibleTasks.add(task);
}
-
- mTmpSortedSet.add(task);
}
/** Constructs a {@link RunningTaskInfo} from a given {@param task}. */
- private RunningTaskInfo createRunningTaskInfo(Task task) {
+ private RunningTaskInfo createRunningTaskInfo(Task task, long visibleActiveTime) {
final RunningTaskInfo rti = new RunningTaskInfo();
task.fillTaskInfo(rti, !mKeepIntentExtra);
+ if (visibleActiveTime > 0) {
+ rti.lastActiveTime = visibleActiveTime;
+ }
// Fill in some deprecated values
rti.id = rti.taskId;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index fbee343..300a894 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,9 @@
*/
Task mAssociatedTask;
+ /** Whether the starting window is drawn. */
+ boolean mIsDisplayed;
+
protected StartingData(WindowManagerService service, int typeParams) {
mService = service;
mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 435ab97..d06f271 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -358,6 +358,13 @@
int mLockTaskUid = -1; // The uid of the application that called startLockTask().
+ /**
+ * If non-null, the starting window should cover the associated task. It is assigned when the
+ * parent activity of starting window is put in a partial area of the task. This field will be
+ * cleared when all visible activities in this task are drawn.
+ */
+ StartingData mSharedStartingData;
+
/** The process that had previously hosted the root activity of this task.
* Used to know that we should try harder to keep this process around, in case the
* user wants to return to it. */
@@ -3673,6 +3680,9 @@
if (mRootProcess != null) {
pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
}
+ if (mSharedStartingData != null) {
+ pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+ }
pw.print(prefix); pw.print("taskId=" + mTaskId);
pw.println(" rootTaskId=" + getRootTaskId());
pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 230b760..063f0db 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1891,10 +1891,10 @@
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
- // There may be a trampoline activity without window on top of the existing task
- // which is moving to front. Exclude the finishing activity so the window of next
- // activity can be chosen to create the animation target.
- ? getTopNonFinishingActivity()
+ // There may be a launching (e.g. trampoline or embedded) activity without a window
+ // on top of the existing task which is moving to front. Exclude finishing activity
+ // so the window of next activity can be chosen to create the animation target.
+ ? getActivity(r -> !r.finishing && r.hasChild())
: getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3590e9c2..7415376 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1547,6 +1547,12 @@
return mTransitionController.mTransitionMetricsReporter;
}
+ @Override
+ public IBinder getApplyToken() {
+ enforceTaskPermission("getApplyToken()");
+ return SurfaceControl.Transaction.getDefaultApplyToken();
+ }
+
/** Whether the configuration changes are important to report back to an organizer. */
static boolean configurationsAreEqualForOrganizer(
Configuration newConfig, @Nullable Configuration oldConfig) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f30c435..0168d50 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1838,8 +1838,8 @@
* @return {@code true} if one or more windows have been displayed, else false.
*/
boolean hasAppShownWindows() {
- return mActivityRecord != null
- && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+ return mActivityRecord != null && (mActivityRecord.firstWindowDrawn
+ || mActivityRecord.isStartingWindowDisplayed());
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 6e16b5d..a0ba8fd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -374,13 +374,6 @@
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- final ActivityRecord activity = mWin.mActivityRecord;
- if (activity != null) {
- if (mWin == activity.mStartingWindow) {
- activity.startingDisplayed = false;
- }
- }
-
if (mSurfaceController == null) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index efcca5d..416d042 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -355,7 +355,7 @@
ProtoOutputStream proto = new ProtoOutputStream();
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
long timeOffsetNs =
- TimeUnit.NANOSECONDS.convert(System.currentTimeMillis(), TimeUnit.NANOSECONDS)
+ TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- SystemClock.elapsedRealtimeNanos();
proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
mBuffer.writeTraceToFile(mTraceFile, proto);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 40412db..321f022 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
@@ -155,5 +156,14 @@
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
return cancelTransport;
}
+
+ @Override
+ public ICancellationSignal clearCredentialSession(
+ IClearCredentialSessionCallback callback, String callingPackage) {
+ // TODO: implement.
+ Log.i(TAG, "clearCredentialSession");
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ return cancelTransport;
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 5b7b8f4..4d92b7f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -25,6 +25,7 @@
import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;
import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
+import static com.android.server.am.BroadcastQueueTest.withPriority;
import static com.google.common.truth.Truth.assertThat;
@@ -46,8 +47,10 @@
import android.os.Bundle;
import android.os.BundleMerger;
import android.os.HandlerThread;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import androidx.test.filters.SmallTest;
@@ -59,6 +62,8 @@
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
@@ -283,7 +288,7 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
queue.setProcessCached(false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -305,12 +310,12 @@
// enqueue a bg-priority broadcast then a fg-priority one
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
- queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, 0);
+ queue.enqueueOrReplaceBroadcast(timezoneRecord, 0);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
// verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -339,9 +344,9 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
- List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
- makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, 1);
+ List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+ withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 1);
assertFalse(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -363,7 +368,7 @@
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+ queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
mConstants.MAX_PENDING_BROADCASTS = 128;
queue.invalidateRunnableAt();
@@ -377,6 +382,55 @@
}
/**
+ * Confirm that we always prefer running pending items marked as "urgent",
+ * then "normal", then "offload", dispatching by the relative ordering
+ * within each of those clustering groups.
+ */
+ @Test
+ public void testMakeActiveNextPending() {
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
+
+ // To maximize test coverage, dump current state; we're not worried
+ // about the actual output, just that we don't crash
+ queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED);
+ queue.dumpLocked(SystemClock.uptimeMillis(),
+ new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream())));
+
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_AIRPLANE_MODE_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+ assertTrue(queue.isEmpty());
+ }
+
+ /**
* Verify that sending a broadcast that removes any matching pending
* broadcasts is applied as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index de59603..fd605f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -503,6 +503,11 @@
return ai;
}
+ static ResolveInfo withPriority(ResolveInfo info, int priority) {
+ info.priority = priority;
+ return info;
+ }
+
static ResolveInfo makeManifestReceiver(String packageName, String name) {
return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
}
@@ -1634,4 +1639,22 @@
waitForIdle();
verify(mAms, never()).enqueueOomAdjTargetLocked(any());
}
+
+ /**
+ * Verify that expected events are triggered when a broadcast is finished.
+ */
+ @Test
+ public void testNotifyFinished() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ final BroadcastRecord record = makeBroadcastRecord(intent, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)));
+ enqueueBroadcast(record);
+
+ waitForIdle();
+ verify(mAms).notifyBroadcastFinishedLocked(eq(record));
+ verify(mAms).addBroadcastStatLocked(eq(Intent.ACTION_TIMEZONE_CHANGED), eq(PACKAGE_RED),
+ eq(1), eq(0), anyLong());
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 11573c5..05ed0e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,9 +24,10 @@
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.isPrioritized;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -99,6 +100,16 @@
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 0))));
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), -10))));
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
+
+ assertArrayEquals(new int[] {-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
+ assertArrayEquals(new int[] {-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
+ assertArrayEquals(new int[] {-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
}
@Test
@@ -111,6 +122,17 @@
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 10),
createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+ assertArrayEquals(new int[] {-1,-1,-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+ assertArrayEquals(new int[] {-1,-1,-1},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
+ createResolveInfo(PACKAGE2, getAppId(2), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
}
@Test
@@ -123,6 +145,19 @@
createResolveInfo(PACKAGE1, getAppId(1), 0),
createResolveInfo(PACKAGE2, getAppId(2), 0),
createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+ assertArrayEquals(new int[] {0,1,2},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), -10),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+ assertArrayEquals(new int[] {0,0,2,3,3},
+ calculateBlockedUntilTerminalCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE2, getAppId(2), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 10),
+ createResolveInfo(PACKAGE3, getAppId(3), 20),
+ createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
}
@Test
@@ -543,4 +578,9 @@
private static int getAppId(int i) {
return Process.FIRST_APPLICATION_UID + i;
}
+
+ private static boolean isPrioritized(List<Object> receivers) {
+ return BroadcastRecord.isPrioritized(
+ calculateBlockedUntilTerminalCount(receivers, false), false);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index c15f6a9..bbcf77b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -326,7 +326,7 @@
void register(Context context) {
if (!mRegistered) {
- context.registerReceiver(this, mFilter);
+ context.registerReceiver(this, mFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
mRegistered = true;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index f28ad79..e54a48b 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -3470,7 +3470,8 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- return mMockContext.registerReceiver(receiver, filter);
+ return mMockContext.registerReceiver(receiver, filter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index 0b84a60..e6ab73a 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -49,6 +49,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
/**
@@ -62,6 +63,7 @@
private AnrHelper mAnrHelper;
private ProcessRecord mAnrApp;
+ private ExecutorService mExecutorService;
@Rule
public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
@@ -88,7 +90,9 @@
return mServiceThreadRule.getThread().getThreadHandler();
}
}, mServiceThreadRule.getThread());
- mAnrHelper = new AnrHelper(service);
+ mExecutorService = mock(ExecutorService.class);
+
+ mAnrHelper = new AnrHelper(service, mExecutorService);
});
}
@@ -119,7 +123,7 @@
verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding(
eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
- eq(parentProcess), eq(aboveSystem), eq(timeoutRecord),
+ eq(parentProcess), eq(aboveSystem), eq(timeoutRecord), eq(mExecutorService),
eq(false) /* onlyDumpSelf */);
}
@@ -133,7 +137,7 @@
processingLatch.await();
return null;
}).when(mAnrApp.mErrorState).appNotResponding(anyString(), any(), any(), any(),
- anyBoolean(), any(), anyBoolean());
+ anyBoolean(), any(), any(), anyBoolean());
final ApplicationInfo appInfo = new ApplicationInfo();
final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
"annotation");
@@ -155,6 +159,7 @@
processingLatch.countDown();
// There is only one ANR reported.
verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS).only()).appNotResponding(
- anyString(), any(), any(), any(), anyBoolean(), any(), anyBoolean());
+ anyString(), any(), any(), any(), anyBoolean(), any(), eq(mExecutorService),
+ anyBoolean());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
index 70519e4..9cada91 100644
--- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
@@ -45,6 +45,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.util.concurrent.ExecutorService;
/**
* Build/Install/Run:
@@ -58,6 +59,7 @@
private ProcessRecord mProcessRecord;
private ProcessErrorStateRecord mProcessErrorState;
+ private ExecutorService mExecutorService;
@BeforeClass
public static void setUpOnce() throws Exception {
@@ -109,6 +111,7 @@
runWithDexmakerShareClassLoader(() -> {
mProcessRecord = new ProcessRecord(sService, sContext.getApplicationInfo(),
"name", 12345);
+ mExecutorService = mock(ExecutorService.class);
mProcessErrorState = spy(mProcessRecord.mErrorState);
doNothing().when(mProcessErrorState).startAppProblemLSP();
doReturn(false).when(mProcessErrorState).isSilentAnr();
@@ -194,11 +197,11 @@
assertTrue(mProcessRecord.isKilled());
}
- private static void appNotResponding(ProcessErrorStateRecord processErrorState,
+ private void appNotResponding(ProcessErrorStateRecord processErrorState,
String annotation) {
TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(annotation);
processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
null /* parentShortComponentName */, null /* parentProcess */,
- false /* aboveSystem */, timeoutRecord, false /* onlyDumpSelf */);
+ false /* aboveSystem */, timeoutRecord, mExecutorService, false /* onlyDumpSelf */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index fe92a1d..935d1d8 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -24,7 +24,6 @@
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.UserHandle.USER_SYSTEM;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -403,7 +402,7 @@
verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
- verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+ verifySystemUserVisibilityChangedNotified(/* visible= */ false);
}
@Test
@@ -424,7 +423,7 @@
verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
- verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+ verifySystemUserVisibilityChangedNotified(/* visible= */ false);
}
@Test
@@ -531,7 +530,7 @@
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
mUserController.getRunningUsersLU());
- verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+ verifySystemUserVisibilityChangedNotified(/* visible= */ false);
}
/**
@@ -964,8 +963,8 @@
verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
}
- private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) {
- verify(mInjector).onUserStarting(userId, visible);
+ private void verifySystemUserVisibilityChangedNotified(boolean visible) {
+ verify(mInjector).notifySystemUserVisibilityChanged(visible);
}
// Should be public to allow mocking
@@ -1108,6 +1107,11 @@
void onUserStarting(@UserIdInt int userId, boolean visible) {
Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
}
+
+ @Override
+ void notifySystemUserVisibilityChanged(boolean visible) {
+ Log.i(TAG, "notifySystemUserVisibilityChanged(" + visible + ")");
+ }
}
private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 675f0e3..608eeec 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -620,6 +620,20 @@
verify(mCallback).onClientFinished(any(), eq(true));
}
+ @Test
+ public void sideFpsPowerPressCancelsIsntantly() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+
+ client.onPowerPressed();
+ mLooper.dispatchAll();
+
+ verify(mCallback, never()).onClientFinished(any(), eq(true));
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index e991ec6..ac1667d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -442,7 +442,8 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
mMockSystemServices.registerReceiver(receiver, filter, null);
- return spiedContext.registerReceiver(receiver, filter);
+ return spiedContext.registerReceiver(receiver, filter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 2b069e3..1fe267f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -57,7 +57,7 @@
import android.hardware.display.DisplayManager.DisplayListener;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Handler;
import android.os.IThermalEventListener;
import android.os.IThermalService;
@@ -1081,10 +1081,10 @@
public void testUdfpsListenerGetsRegistered() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
- verify(mStatusBarMock, never()).setUdfpsHbmListener(any());
+ verify(mStatusBarMock, never()).setUdfpsRefreshRateCallback(any());
director.onBootCompleted();
- verify(mStatusBarMock).setUdfpsHbmListener(eq(director.getUdpfsObserver()));
+ verify(mStatusBarMock).setUdfpsRefreshRateCallback(eq(director.getUdpfsObserver()));
}
@Test
@@ -1093,10 +1093,9 @@
createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
director.start(createMockSensorManager());
director.onBootCompleted();
- ArgumentCaptor<IUdfpsHbmListener> captor =
- ArgumentCaptor.forClass(IUdfpsHbmListener.class);
- verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
- IUdfpsHbmListener hbmListener = captor.getValue();
+ ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+ ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+ verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
// Should be no vote initially
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
index d91a748..9c8e72c 100644
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
@@ -115,7 +115,8 @@
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_JOB_STARTED);
intentFilter.addAction(ACTION_JOB_STOPPED);
- mContext.registerReceiver(mJobStateChangeReceiver, intentFilter);
+ mContext.registerReceiver(mJobStateChangeReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
setAppOpsModeAllowed(true);
setPowerExemption(false);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
index ea3c5fa..d9ebb4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverySnapshotListenersStorageTest.java
@@ -62,7 +62,7 @@
context.unregisterReceiver(this);
latch.countDown();
}
- }, new IntentFilter(TEST_INTENT_ACTION));
+ }, new IntentFilter(TEST_INTENT_ACTION), Context.RECEIVER_EXPORTED_UNAUDITED);
mStorage.setSnapshotListener(recoveryAgentUid, intent);
@@ -83,7 +83,8 @@
latch.countDown();
}
};
- context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION));
+ context.registerReceiver(broadcastReceiver, new IntentFilter(TEST_INTENT_ACTION),
+ Context.RECEIVER_EXPORTED_UNAUDITED);
mStorage.setSnapshotListener(recoveryAgentUid, intent);
mStorage.setSnapshotListener(recoveryAgentUid, intent);
diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
index eaa0e9b..f0d389b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
@@ -34,6 +34,7 @@
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.pkg.PackageStateUnserialized;
import com.android.server.pm.pkg.PackageUserStateImpl;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -46,12 +47,16 @@
private boolean mCompatibilityModeEnabled;;
private PackageImpl mMockAndroidPackage;
+ private PackageSetting mMockPackageState;
private PackageUserStateImpl mMockUserState;
@Before
public void setUp() {
mCompatibilityModeEnabled = ParsingPackageUtils.sCompatibilityModeEnabled;
mMockAndroidPackage = mock(PackageImpl.class);
+ mMockPackageState = mock(PackageSetting.class);
+ when(mMockPackageState.getTransientState())
+ .thenReturn(new PackageStateUnserialized(mMockPackageState));
mMockUserState = new PackageUserStateImpl();
mMockUserState.setInstalled(true);
}
@@ -221,7 +226,7 @@
info.flags |= flags;
when(mMockAndroidPackage.toAppInfoWithoutState()).thenReturn(info);
return PackageInfoUtils.generateApplicationInfo(mMockAndroidPackage,
- 0 /*flags*/, mMockUserState, 0 /*userId*/, null);
+ 0 /*flags*/, mMockUserState, 0 /*userId*/, mMockPackageState);
}
private void setGlobalCompatibilityMode(boolean enabled) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 9ce99d6..59f27ec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -676,27 +676,32 @@
final File testFile = extractFile(TEST_APP4_APK);
try {
final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+ var pkgSetting = mockPkgSetting(pkg);
ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
- PackageUserStateInternal.DEFAULT, 0, null);
+ PackageUserStateInternal.DEFAULT, 0, pkgSetting);
for (ParsedActivity activity : pkg.getActivities()) {
assertNotNull(activity.getMetaData());
assertNull(PackageInfoUtils.generateActivityInfo(pkg, activity, 0,
- PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+ PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+ .metaData);
}
for (ParsedProvider provider : pkg.getProviders()) {
assertNotNull(provider.getMetaData());
assertNull(PackageInfoUtils.generateProviderInfo(pkg, provider, 0,
- PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+ PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+ .metaData);
}
for (ParsedActivity receiver : pkg.getReceivers()) {
assertNotNull(receiver.getMetaData());
assertNull(PackageInfoUtils.generateActivityInfo(pkg, receiver, 0,
- PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+ PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+ .metaData);
}
for (ParsedService service : pkg.getServices()) {
assertNotNull(service.getMetaData());
assertNull(PackageInfoUtils.generateServiceInfo(pkg, service, 0,
- PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+ PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+ .metaData);
}
} finally {
testFile.delete();
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 8efcc41..8d2cffa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -59,9 +59,11 @@
final UserProperties defaultProps = new UserProperties.Builder()
.setShowInLauncher(21)
.setStartWithParent(false)
+ .setShowInSettings(45)
.build();
final UserProperties actualProps = new UserProperties(defaultProps);
actualProps.setShowInLauncher(14);
+ actualProps.setShowInSettings(32);
// Write the properties to xml.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -99,10 +101,12 @@
final UserProperties defaultProps = new UserProperties.Builder()
.setShowInLauncher(2145)
.setStartWithParent(true)
+ .setShowInSettings(3452)
.build();
final UserProperties orig = new UserProperties(defaultProps);
orig.setShowInLauncher(2841);
orig.setStartWithParent(false);
+ orig.setShowInSettings(1437);
// Test every permission level. (Currently, it's linear so it's easy.)
for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -140,6 +144,9 @@
assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
// Items requiring hasManagePermission - put them here using hasManagePermission.
+ assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
+ hasManagePermission);
+
// Items requiring hasQueryPermission - put them here using hasQueryPermission.
// Items with no permission requirements.
@@ -182,5 +189,6 @@
assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+ assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index c1e778d..df2c5dd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -599,6 +599,7 @@
// Check that this new user has the expected properties (relative to the defaults)
// provided that the test caller has the necessary permissions.
assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+ assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
assertThrows(SecurityException.class, userProps::getStartWithParent);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 9cd97ff3..699601b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -20,21 +20,16 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.apex.ApexInfo;
import android.content.Context;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
-import android.content.pm.SigningDetails;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Build;
import android.os.Bundle;
import android.os.FileUtils;
-import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import android.util.SparseIntArray;
@@ -53,7 +48,6 @@
import com.android.server.pm.pkg.component.ParsedIntentInfo;
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.google.common.truth.Expect;
@@ -63,7 +57,6 @@
import java.io.File;
import java.io.InputStream;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -567,54 +560,6 @@
}
@Test
- public void testApexPackageInfoGeneration() throws Exception {
- String apexModuleName = "com.android.tzdata.apex";
- File apexFile = copyRawResourceToFile(apexModuleName,
- R.raw.com_android_tzdata);
- ApexInfo apexInfo = new ApexInfo();
- apexInfo.isActive = true;
- apexInfo.isFactory = false;
- apexInfo.moduleName = apexModuleName;
- apexInfo.modulePath = apexFile.getPath();
- apexInfo.versionCode = 191000070;
- int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
-
- ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile,
- flags, Collections.emptyList(), false /*collectCertificates*/);
- if (result.isError()) {
- throw new IllegalStateException(result.getErrorMessage(), result.getException());
- }
-
- ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- ParsedPackage pkg = result.getResult();
- ParseResult<SigningDetails> ret = ParsingPackageUtils.getSigningDetails(
- input, pkg, false /*skipVerify*/);
- if (ret.isError()) {
- throw new IllegalStateException(ret.getErrorMessage(), ret.getException());
- }
- pkg.setSigningDetails(ret.getResult());
- PackageInfo pi = PackageInfoUtils.generate(pkg.setApex(true).hideAsFinal(), apexInfo,
- flags, null, UserHandle.USER_SYSTEM);
-
- assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
- assertTrue(pi.applicationInfo.enabled);
- assertEquals(28, pi.applicationInfo.targetSdkVersion);
- assertEquals(191000070, pi.applicationInfo.longVersionCode);
- assertNotNull(pi.applicationInfo.metaData);
- assertEquals(apexFile.getPath(), pi.applicationInfo.sourceDir);
- assertEquals("Bundle[{com.android.vending.derived.apk.id=1}]",
- pi.applicationInfo.metaData.toString());
-
- assertEquals("com.google.android.tzdata", pi.packageName);
- assertEquals(191000070, pi.getLongVersionCode());
- assertNotNull(pi.signingInfo);
- assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
- assertTrue(pi.isApex);
- assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
- assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
- }
-
- @Test
public void testUsesSdk() throws Exception {
ParsedPackage pkg;
SparseIntArray minExtVers;
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
index a98a43b..93464cd 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
@@ -25,7 +25,7 @@
import android.app.time.TimeCapabilitiesAndConfig;
import android.app.time.TimeConfiguration;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
import java.util.ArrayList;
import java.util.List;
@@ -33,17 +33,17 @@
/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
- private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+ private final List<StateChangeListener> mConfigurationInternalChangeListeners =
new ArrayList<>();
private ConfigurationInternal mConfigurationInternal;
@Override
- public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void addConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.add(listener);
}
@Override
- public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.remove(listener);
}
@@ -86,7 +86,7 @@
}
void simulateConfigurationChangeForTests() {
- for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
+ for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
listener.onChange();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 62dae48..caef494 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -40,7 +40,7 @@
import com.android.server.SystemClockTime.TimeConfidence;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
import org.junit.Before;
import org.junit.Test;
@@ -1821,7 +1821,7 @@
private long mElapsedRealtimeMillis;
private long mSystemClockMillis;
private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
- private ConfigurationChangeListener mConfigurationInternalChangeListener;
+ private StateChangeListener mConfigurationInternalChangeListener;
// Tracking operations.
private boolean mSystemClockWasSet;
@@ -1837,7 +1837,7 @@
}
@Override
- public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void setConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListener = Objects.requireNonNull(listener);
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 7140097..153d746 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -57,7 +57,8 @@
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_autoDetectionSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
- ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
@@ -82,10 +83,7 @@
assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -101,7 +99,7 @@
assertEquals(CAPABILITY_POSSESSED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -117,10 +115,8 @@
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities =
+ autoOffConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -136,7 +132,7 @@
assertEquals(CAPABILITY_NOT_APPLICABLE,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -150,7 +146,8 @@
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_autoDetectNotSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
- ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
@@ -175,10 +172,7 @@
assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
if (userRestrictionsExpected) {
@@ -189,7 +183,7 @@
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -205,10 +199,8 @@
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities =
+ autoOffConfig.asCapabilities(bypassUserPolicyChecks);
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureAutoDetectionEnabledCapability());
if (userRestrictionsExpected) {
@@ -219,7 +211,7 @@
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -233,7 +225,8 @@
@Parameters({ "true,true", "true,false", "false,true", "false,false" })
public void test_geoDetectNotSupported_capabilitiesAndConfiguration(
boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
- ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(userConfigAllowed)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
@@ -258,10 +251,7 @@
assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -276,7 +266,7 @@
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
assertTrue(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -292,10 +282,8 @@
assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneCapabilities capabilities =
+ autoOffConfig.asCapabilities(bypassUserPolicyChecks);
if (userRestrictionsExpected) {
assertEquals(CAPABILITY_NOT_ALLOWED,
capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -308,7 +296,7 @@
assertEquals(CAPABILITY_NOT_SUPPORTED,
capabilities.getConfigureGeoDetectionEnabledCapability());
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
assertFalse(configuration.isAutoDetectionEnabled());
assertTrue(configuration.isGeoDetectionEnabled());
}
@@ -316,7 +304,8 @@
@Test
public void test_telephonyFallbackSupported() {
- ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal config = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
@@ -331,7 +320,8 @@
/** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
@Test
public void test_geoDetectionRunInBackgroundEnabled() {
- ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
index fdee86e..fc6afe4 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
@@ -16,14 +16,11 @@
package com.android.server.timezonedetector;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
import java.time.Duration;
@@ -31,76 +28,104 @@
import java.util.List;
import java.util.Optional;
-/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
+/**
+ * A partially implemented, fake implementation of ServiceConfigAccessor for tests.
+ *
+ * <p>This class has rudamentary support for multiple users, but unlike the real thing, it doesn't
+ * simulate that some settings are global and shared between users. It also delivers config updates
+ * synchronously.
+ */
public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
- private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+ private final List<StateChangeListener> mConfigurationInternalChangeListeners =
new ArrayList<>();
- private ConfigurationInternal mConfigurationInternal;
+ private ConfigurationInternal mCurrentUserConfigurationInternal;
+ private ConfigurationInternal mOtherUserConfigurationInternal;
@Override
- public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void addConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.add(listener);
}
@Override
- public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+ public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
mConfigurationInternalChangeListeners.remove(listener);
}
@Override
public ConfigurationInternal getCurrentUserConfigurationInternal() {
- return mConfigurationInternal;
+ return getConfigurationInternal(mCurrentUserConfigurationInternal.getUserId());
}
@Override
public boolean updateConfiguration(
- @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges,
+ @UserIdInt int userId, @NonNull TimeZoneConfiguration requestedChanges,
boolean bypassUserPolicyChecks) {
- assertNotNull(mConfigurationInternal);
+ assertNotNull(mCurrentUserConfigurationInternal);
assertNotNull(requestedChanges);
+ ConfigurationInternal toUpdate = getConfigurationInternal(userId);
+
// Simulate the real strategy's behavior: the new configuration will be updated to be the
- // old configuration merged with the new if the user has the capability to up the settings.
- // Then, if the configuration changed, the change listener is invoked.
- TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
- mConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
- TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
- TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ // old configuration merged with the new if the user has the capability to update the
+ // settings. Then, if the configuration changed, the change listener is invoked.
+ TimeZoneCapabilities capabilities = toUpdate.asCapabilities(bypassUserPolicyChecks);
+ TimeZoneConfiguration configuration = toUpdate.asConfiguration();
TimeZoneConfiguration newConfiguration =
capabilities.tryApplyConfigChanges(configuration, requestedChanges);
if (newConfiguration == null) {
return false;
}
- if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
- mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
-
+ if (!newConfiguration.equals(configuration)) {
+ ConfigurationInternal updatedConfiguration = toUpdate.merge(newConfiguration);
+ if (updatedConfiguration.getUserId() == mCurrentUserConfigurationInternal.getUserId()) {
+ mCurrentUserConfigurationInternal = updatedConfiguration;
+ } else if (mOtherUserConfigurationInternal != null
+ && updatedConfiguration.getUserId()
+ == mOtherUserConfigurationInternal.getUserId()) {
+ mOtherUserConfigurationInternal = updatedConfiguration;
+ }
// Note: Unlike the real strategy, the listeners are invoked synchronously.
- simulateConfigurationChangeForTests();
+ notifyConfigurationChange();
}
return true;
}
- void initializeConfiguration(ConfigurationInternal configurationInternal) {
- mConfigurationInternal = configurationInternal;
+ void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) {
+ mCurrentUserConfigurationInternal = configurationInternal;
}
- void simulateConfigurationChangeForTests() {
- for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
- listener.onChange();
- }
+ void initializeOtherUserConfiguration(ConfigurationInternal configurationInternal) {
+ mOtherUserConfigurationInternal = configurationInternal;
+ }
+
+ void simulateCurrentUserConfigurationInternalChange(
+ ConfigurationInternal configurationInternal) {
+ mCurrentUserConfigurationInternal = configurationInternal;
+ // Note: Unlike the real strategy, the listeners are invoked synchronously.
+ notifyConfigurationChange();
+ }
+
+ void simulateOtherUserConfigurationInternalChange(ConfigurationInternal configurationInternal) {
+ mOtherUserConfigurationInternal = configurationInternal;
+ // Note: Unlike the real strategy, the listeners are invoked synchronously.
+ notifyConfigurationChange();
}
@Override
public ConfigurationInternal getConfigurationInternal(int userId) {
- assertEquals("Multi-user testing not supported currently",
- userId, mConfigurationInternal.getUserId());
- return mConfigurationInternal;
+ if (userId == mCurrentUserConfigurationInternal.getUserId()) {
+ return mCurrentUserConfigurationInternal;
+ } else if (mOtherUserConfigurationInternal != null
+ && userId == mOtherUserConfigurationInternal.getUserId()) {
+ return mOtherUserConfigurationInternal;
+ }
+ throw new AssertionError("userId not known: " + userId);
}
@Override
- public void addLocationTimeZoneManagerConfigListener(ConfigurationChangeListener listener) {
+ public void addLocationTimeZoneManagerConfigListener(StateChangeListener listener) {
failUnimplemented();
}
@@ -206,9 +231,14 @@
failUnimplemented();
}
+ private void notifyConfigurationChange() {
+ for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
+ listener.onChange();
+ }
+ }
+
@SuppressWarnings("UnusedReturnValue")
private static <T> T failUnimplemented() {
- fail("Unimplemented");
- return null;
+ throw new AssertionError("Unimplemented");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index 228dc95..fed8b40 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -15,23 +15,71 @@
*/
package com.android.server.timezonedetector;
+import static org.junit.Assert.assertEquals;
+
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
+import java.util.ArrayList;
+
public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
+ private final FakeServiceConfigAccessor mFakeServiceConfigAccessor =
+ new FakeServiceConfigAccessor();
+ private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
private TimeZoneState mTimeZoneState;
+ public FakeTimeZoneDetectorStrategy() {
+ mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
+ this::notifyChangeListeners);
+ }
+
+ public void initializeConfiguration(ConfigurationInternal configuration) {
+ mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+ }
+
@Override
public boolean confirmTimeZone(String timeZoneId) {
return false;
}
@Override
+ public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
+ boolean bypassUserPolicyChecks) {
+ ConfigurationInternal configurationInternal =
+ mFakeServiceConfigAccessor.getCurrentUserConfigurationInternal();
+ assertEquals("Multi-user testing not supported",
+ configurationInternal.getUserId(), userId);
+ return new TimeZoneCapabilitiesAndConfig(
+ configurationInternal.asCapabilities(bypassUserPolicyChecks),
+ configurationInternal.asConfiguration());
+ }
+
+ @Override
+ public boolean updateConfiguration(int userId, TimeZoneConfiguration requestedChanges,
+ boolean bypassUserPolicyChecks) {
+ return mFakeServiceConfigAccessor.updateConfiguration(
+ userId, requestedChanges, bypassUserPolicyChecks);
+ }
+
+ @Override
+ public void addChangeListener(StateChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ private void notifyChangeListeners() {
+ for (StateChangeListener listener : mListeners) {
+ listener.onChange();
+ }
+ }
+
+ @Override
public TimeZoneState getTimeZoneState() {
return mTimeZoneState;
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index 782eebf..223c532 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -161,7 +161,8 @@
private static ConfigurationInternal createConfigurationInternal(
boolean enhancedMetricsCollectionEnabled) {
- return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ return new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
index 21c9685..eb6f00c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
@@ -66,10 +66,14 @@
/**
* Waits for all enqueued work to be completed before returning.
*/
- public void waitForMessagesToBeProcessed() throws InterruptedException {
+ public void waitForMessagesToBeProcessed() {
synchronized (mMonitor) {
if (mMessagesSent != mMessagesProcessed) {
- mMonitor.wait();
+ try {
+ mMonitor.wait();
+ } catch (InterruptedException e) {
+ throw new AssertionError("Unexpected exception", e);
+ }
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 276fdb9..8909832 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -50,7 +50,6 @@
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCurrentUserIdentityInjector mTestCurrentUserIdentityInjector;
- private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
private TimeZoneDetectorInternalImpl mTimeZoneDetectorInternal;
@@ -65,12 +64,11 @@
mTestHandler = new TestHandler(mHandlerThread.getLooper());
mTestCurrentUserIdentityInjector = new TestCurrentUserIdentityInjector();
mTestCurrentUserIdentityInjector.initializeCurrentUserId(ARBITRARY_USER_ID);
- mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
mTimeZoneDetectorInternal = new TimeZoneDetectorInternalImpl(
mMockContext, mTestHandler, mTestCurrentUserIdentityInjector,
- mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+ mFakeTimeZoneDetectorStrategySpy);
}
@After
@@ -83,17 +81,20 @@
public void testGetCapabilitiesAndConfigForDpm() throws Exception {
final boolean autoDetectionEnabled = true;
ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
- mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
int expectedUserId = mTestCurrentUserIdentityInjector.getCurrentUserId();
- verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
+ final boolean expectedBypassUserPolicyChecks = true;
+ verify(mFakeTimeZoneDetectorStrategySpy).getCapabilitiesAndConfig(
+ expectedUserId, expectedBypassUserPolicyChecks);
- final boolean bypassUserPolicyChecks = true;
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
- testConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+ new TimeZoneCapabilitiesAndConfig(
+ testConfig.asCapabilities(expectedBypassUserPolicyChecks),
+ testConfig.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
}
@@ -102,7 +103,7 @@
final boolean autoDetectionEnabled = false;
ConfigurationInternal initialConfigurationInternal =
createConfigurationInternal(autoDetectionEnabled);
- mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
@@ -110,7 +111,7 @@
assertTrue(mTimeZoneDetectorInternal.updateConfigurationForDpm(timeConfiguration));
final boolean expectedBypassUserPolicyChecks = true;
- verify(mFakeServiceConfigAccessorSpy).updateConfiguration(
+ verify(mFakeTimeZoneDetectorStrategySpy).updateConfiguration(
mTestCurrentUserIdentityInjector.getCurrentUserId(),
timeConfiguration,
expectedBypassUserPolicyChecks);
@@ -148,7 +149,8 @@
}
private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
- return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ return new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index bb9d564..d8346ee 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -69,7 +69,6 @@
private HandlerThread mHandlerThread;
private TestHandler mTestHandler;
private TestCallerIdentityInjector mTestCallerIdentityInjector;
- private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
@@ -85,12 +84,11 @@
mTestCallerIdentityInjector = new TestCallerIdentityInjector();
mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
- mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
mTimeZoneDetectorService = new TimeZoneDetectorService(
mMockContext, mTestHandler, mTestCallerIdentityInjector,
- mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+ mFakeTimeZoneDetectorStrategySpy);
}
@After
@@ -115,7 +113,7 @@
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
- mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -124,11 +122,14 @@
eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
- verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
-
boolean expectedBypassUserPolicyChecks = false;
+ verify(mFakeTimeZoneDetectorStrategySpy)
+ .getCapabilitiesAndConfig(expectedUserId, expectedBypassUserPolicyChecks);
+
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
- configuration.createCapabilitiesAndConfig(expectedBypassUserPolicyChecks);
+ new TimeZoneCapabilitiesAndConfig(
+ configuration.asCapabilities(expectedBypassUserPolicyChecks),
+ configuration.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
}
@@ -160,7 +161,7 @@
public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
- mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -455,7 +456,8 @@
// Default geo detection settings from auto detection settings - they are not important to
// the tests.
final boolean geoDetectionEnabled = autoDetectionEnabled;
- return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ return new ConfigurationInternal.Builder()
+ .setUserId(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index d0a7c92..f50e7fb 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -38,19 +38,28 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
+import android.os.HandlerThread;
import com.android.server.SystemTimeZone.TimeZoneConfidence;
import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -97,7 +106,8 @@
};
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -110,7 +120,8 @@
.build();
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -123,7 +134,8 @@
.build();
private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
.setTelephonyFallbackSupported(false)
@@ -136,7 +148,8 @@
.build();
private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -149,7 +162,8 @@
.build();
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -162,7 +176,8 @@
.build();
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
- new ConfigurationInternal.Builder(USER_ID)
+ new ConfigurationInternal.Builder()
+ .setUserId(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setTelephonyFallbackSupported(false)
@@ -174,14 +189,223 @@
.setGeoDetectionEnabledSetting(true)
.build();
- private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
+ private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeEnvironment mFakeEnvironment;
+ private HandlerThread mHandlerThread;
+ private TestHandler mTestHandler;
+
+ private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
@Before
public void setUp() {
mFakeEnvironment = new FakeEnvironment();
- mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED_GEO_DISABLED);
- mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
+ mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
+ mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(
+ CONFIG_AUTO_DISABLED_GEO_DISABLED);
+
+ // Create a thread + handler for processing the work that the strategy posts.
+ mHandlerThread = new HandlerThread("TimeZoneDetectorStrategyImplTest");
+ mHandlerThread.start();
+ mTestHandler = new TestHandler(mHandlerThread.getLooper());
+ mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(
+ mFakeServiceConfigAccessorSpy, mTestHandler, mFakeEnvironment);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ mHandlerThread.join();
+ }
+
+ @Test
+ public void testChangeListenerBehavior_currentUser() throws Exception {
+ ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+ // The strategy initializes itself with the current user's config during construction.
+ assertEquals(currentUserConfig,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+ boolean bypassUserPolicyChecks = false;
+
+ // Report a config change, but not one that actually changes anything.
+ {
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ CONFIG_AUTO_DISABLED_GEO_DISABLED);
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceived(0);
+ assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+ }
+
+ // Report a config change that actually changes something.
+ {
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ CONFIG_AUTO_ENABLED_GEO_ENABLED);
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceived(1);
+ stateChangeListener.resetNotificationsReceivedCount();
+ assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+ }
+
+ // Perform a (current user) update via the strategy.
+ {
+ TimeZoneConfiguration requestedChanges =
+ new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+ mTimeZoneDetectorStrategy.updateConfiguration(
+ USER_ID, requestedChanges, bypassUserPolicyChecks);
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceived(1);
+ stateChangeListener.resetNotificationsReceivedCount();
+ }
+ }
+
+ // Perform a (not current user) update via the strategy. There's no listener behavior for
+ // updates to "other" users.
+ @Test
+ public void testChangeListenerBehavior_otherUser() throws Exception {
+ ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+ // The strategy initializes itself with the current user's config during construction.
+ assertEquals(currentUserConfig,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+ boolean bypassUserPolicyChecks = false;
+
+ int otherUserId = currentUserConfig.getUserId() + 1;
+ ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+ .setUserId(otherUserId)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+ mFakeServiceConfigAccessorSpy.initializeOtherUserConfiguration(otherUserConfig);
+
+ TimeZoneConfiguration requestedChanges =
+ new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+ mTimeZoneDetectorStrategy.updateConfiguration(
+ otherUserId, requestedChanges, bypassUserPolicyChecks);
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ // Only changes to the current user's config are notified.
+ stateChangeListener.assertNotificationsReceived(0);
+ stateChangeListener.resetNotificationsReceivedCount();
+ }
+
+ // Current user behavior: the strategy caches and returns the latest configuration.
+ @Test
+ public void testReadAndWriteConfiguration_currentUser() throws Exception {
+ ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ currentUserConfig);
+
+ int otherUserId = currentUserConfig.getUserId() + 1;
+ ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+ .setUserId(otherUserId)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+ mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+ reset(mFakeServiceConfigAccessorSpy);
+
+ final boolean bypassUserPolicyChecks = false;
+
+ ConfigurationInternal cachedConfigurationInternal =
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+ assertEquals(currentUserConfig, cachedConfigurationInternal);
+
+ // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+ {
+ reset(mFakeServiceConfigAccessorSpy);
+ TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+ mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+ currentUserConfig.getUserId(), bypassUserPolicyChecks);
+ verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal(
+ currentUserConfig.getUserId());
+
+ assertEquals(currentUserConfig.asCapabilities(bypassUserPolicyChecks),
+ actualCapabilitiesAndConfig.getCapabilities());
+ assertEquals(currentUserConfig.asConfiguration(),
+ actualCapabilitiesAndConfig.getConfiguration());
+ }
+
+ // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates
+ // the cached copy.
+ {
+ boolean newGeoDetectionEnabled =
+ !cachedConfigurationInternal.asConfiguration().isGeoDetectionEnabled();
+ TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+ .setGeoDetectionEnabled(newGeoDetectionEnabled)
+ .build();
+ ConfigurationInternal expectedConfigAfterChange =
+ new ConfigurationInternal.Builder(cachedConfigurationInternal)
+ .setGeoDetectionEnabledSetting(newGeoDetectionEnabled)
+ .build();
+
+ reset(mFakeServiceConfigAccessorSpy);
+ mTimeZoneDetectorStrategy.updateConfiguration(
+ currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+ verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+ currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+ assertEquals(expectedConfigAfterChange,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+ }
+ }
+
+ // Not current user behavior: the strategy reads from the ServiceConfigAccessor.
+ @Test
+ public void testReadAndWriteConfiguration_otherUser() throws Exception {
+ ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ currentUserConfig);
+
+ int otherUserId = currentUserConfig.getUserId() + 1;
+ ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+ .setUserId(otherUserId)
+ .setGeoDetectionEnabledSetting(true)
+ .build();
+ mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+ reset(mFakeServiceConfigAccessorSpy);
+
+ final boolean bypassUserPolicyChecks = false;
+
+ // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+ {
+ reset(mFakeServiceConfigAccessorSpy);
+ TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+ mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+ otherUserId, bypassUserPolicyChecks);
+ verify(mFakeServiceConfigAccessorSpy, times(1)).getConfigurationInternal(otherUserId);
+
+ assertEquals(otherUserConfig.asCapabilities(bypassUserPolicyChecks),
+ actualCapabilitiesAndConfig.getCapabilities());
+ assertEquals(otherUserConfig.asConfiguration(),
+ actualCapabilitiesAndConfig.getConfiguration());
+ }
+
+ // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and doesn't
+ // touch the cached copy.
+ {
+ ConfigurationInternal cachedConfigBeforeChange =
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+ boolean newGeoDetectionEnabled =
+ !otherUserConfig.asConfiguration().isGeoDetectionEnabled();
+ TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+ .setGeoDetectionEnabled(newGeoDetectionEnabled)
+ .build();
+
+ reset(mFakeServiceConfigAccessorSpy);
+ mTimeZoneDetectorStrategy.updateConfiguration(
+ currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+ verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+ currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+ assertEquals(cachedConfigBeforeChange,
+ mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+ }
}
@Test
@@ -1201,19 +1425,13 @@
private final TestState<String> mTimeZoneId = new TestState<>();
private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
- private ConfigurationInternal mConfigurationInternal;
private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
- private ConfigurationChangeListener mConfigurationInternalChangeListener;
FakeEnvironment() {
// Ensure the fake environment starts with the defaults a fresh device would.
initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
}
- void initializeConfig(ConfigurationInternal configurationInternal) {
- mConfigurationInternal = configurationInternal;
- }
-
void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
mElapsedRealtimeMillis = elapsedRealtimeMillis;
}
@@ -1228,16 +1446,6 @@
}
@Override
- public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
- mConfigurationInternalChangeListener = listener;
- }
-
- @Override
- public ConfigurationInternal getCurrentUserConfigurationInternal() {
- return mConfigurationInternal;
- }
-
- @Override
public String getDeviceTimeZone() {
return mTimeZoneId.getLatest();
}
@@ -1254,11 +1462,6 @@
mTimeZoneConfidence.set(confidence);
}
- void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
- mConfigurationInternal = configurationInternal;
- mConfigurationInternalChangeListener.onChange();
- }
-
void assertTimeZoneNotChanged() {
mTimeZoneId.assertHasNotBeenSet();
mTimeZoneConfidence.assertHasNotBeenSet();
@@ -1322,7 +1525,8 @@
* Simulates the user / user's configuration changing.
*/
Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
- mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+ mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+ configurationInternal);
return this;
}
@@ -1331,7 +1535,7 @@
*/
Script simulateSetAutoMode(boolean autoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
- mFakeEnvironment.getCurrentUserConfigurationInternal())
+ mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
.setAutoDetectionEnabledSetting(autoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
@@ -1343,7 +1547,7 @@
*/
Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
- mFakeEnvironment.getCurrentUserConfigurationInternal())
+ mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
.setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
@@ -1457,4 +1661,22 @@
@MatchType int matchType, @Quality int quality, int expectedScore) {
return new TelephonyTestCase(matchType, quality, expectedScore);
}
+
+ private static class TestStateChangeListener implements StateChangeListener {
+
+ private int mNotificationsReceived;
+
+ @Override
+ public void onChange() {
+ mNotificationsReceived++;
+ }
+
+ public void resetNotificationsReceivedCount() {
+ mNotificationsReceived = 0;
+ }
+
+ public void assertNotificationsReceived(int expectedCount) {
+ assertEquals(expectedCount, mNotificationsReceived);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index 2c3a7c4..042e3ef8 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -42,7 +42,8 @@
private static ConfigurationInternal createUserConfig(
@UserIdInt int userId, boolean geoDetectionEnabledSetting) {
- return new ConfigurationInternal.Builder(userId)
+ return new ConfigurationInternal.Builder()
+ .setUserId(userId)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4ec9762..3a8e1cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2884,6 +2884,7 @@
fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
task.addChild(taskFragment1, POSITION_TOP);
assertEquals(task, activity1.mStartingData.mAssociatedTask);
+ assertEquals(activity1.mStartingData, task.mSharedStartingData);
final TaskFragment taskFragment2 = new TaskFragment(
mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2903,7 +2904,6 @@
verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
eq(task.mSurfaceControl));
- assertEquals(activity1.mStartingData, startingWindow.mStartingData);
assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
assertEquals(taskFragment1.getBounds(), activity1.getBounds());
// The activity was resized by task fragment, but starting window must still cover the task.
@@ -2914,6 +2914,7 @@
activity1.onFirstWindowDrawn(activityWindow);
activity2.onFirstWindowDrawn(activityWindow);
assertNull(activity1.mStartingWindow);
+ assertNull(task.mSharedStartingData);
}
@Test
@@ -2989,10 +2990,10 @@
final WindowManager.LayoutParams attrs =
new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
final TestWindowState startingWindow = createWindowState(attrs, activity);
- activity.startingDisplayed = true;
+ activity.mStartingData = mock(StartingData.class);
activity.addWindow(startingWindow);
assertTrue("Starting window should be present", activity.hasStartingWindow());
- activity.startingDisplayed = false;
+ activity.mStartingData = null;
assertTrue("Starting window should be present", activity.hasStartingWindow());
activity.removeChild(startingWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 2b0e76c..053718e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -98,6 +98,7 @@
import android.util.Pair;
import android.util.Size;
import android.view.Gravity;
+import android.view.RemoteAnimationAdapter;
import android.window.TaskFragmentOrganizerToken;
import androidx.test.filters.SmallTest;
@@ -1323,6 +1324,32 @@
}
@Test
+ public void testRemoteAnimation_appliesToExistingTask() {
+ final ActivityStarter starter = prepareStarter(0, false);
+
+ // Put an activity on default display as the top focused activity.
+ ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Intent intent = new Intent();
+ intent.setComponent(ActivityBuilder.getDefaultComponent());
+ starter.setReason("testRemoteAnimation_newTask")
+ .setIntent(intent)
+ .execute();
+
+ assertNull(mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation);
+
+ // Relaunch the activity with remote animation indicated in options.
+ final RemoteAnimationAdapter adaptor = mock(RemoteAnimationAdapter.class);
+ final ActivityOptions options = ActivityOptions.makeRemoteAnimation(adaptor);
+ starter.setReason("testRemoteAnimation_existingTask")
+ .setIntent(intent)
+ .setActivityOptions(options.toBundle())
+ .execute();
+
+ // Verify the remote animation is updated.
+ assertEquals(adaptor, mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation);
+ }
+
+ @Test
public void testStartLaunchIntoPipActivity() {
final ActivityStarter starter = prepareStarter(0, false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 513791d..0332c4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -1300,6 +1300,8 @@
activity.allDrawn = true;
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
+ // Assume the activity contains a window.
+ doReturn(true).when(activity).hasChild();
// Make sure activity can create remote animation target.
doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
any());
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 f61effa..32c95fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -16,8 +16,6 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -27,7 +25,6 @@
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
@@ -321,7 +318,6 @@
final ActivityRecord activity2 = createActivityRecord(dc2);
activity1.allDrawn = true;
- activity1.startingDisplayed = true;
activity1.startingMoved = true;
// Simulate activity resume / finish flows to prepare app transition & set visibility,
@@ -412,50 +408,38 @@
}
@Test
- public void testExcludeLauncher() {
+ public void testDelayWhileRecents() {
final DisplayContent dc = createNewDisplay(Display.STATE_ON);
doReturn(false).when(dc).onDescendantOrientationChanged(any());
final Task task = createTask(dc);
- // Simulate activity1 launches activity2
+ // Simulate activity1 launches activity2.
final ActivityRecord activity1 = createActivityRecord(task);
activity1.setVisible(true);
activity1.mVisibleRequested = false;
activity1.allDrawn = true;
- dc.mClosingApps.add(activity1);
final ActivityRecord activity2 = createActivityRecord(task);
activity2.setVisible(false);
activity2.mVisibleRequested = true;
activity2.allDrawn = true;
+
+ dc.mClosingApps.add(activity1);
dc.mOpeningApps.add(activity2);
dc.prepareAppTransition(TRANSIT_OPEN);
-
- // Simulate start recents
- final ActivityRecord homeActivity = createActivityRecord(dc, WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_HOME);
- homeActivity.setVisible(false);
- homeActivity.mVisibleRequested = true;
- homeActivity.allDrawn = true;
- dc.mOpeningApps.add(homeActivity);
- dc.prepareAppTransition(TRANSIT_NONE);
- doReturn(true).when(task)
- .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
+ assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
// Wait until everything in animation handler get executed to prevent the exiting window
// from being removed during WindowSurfacePlacer Traversal.
waitUntilHandlersIdle();
+ // Start recents
+ doReturn(true).when(task)
+ .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));
+
dc.mAppTransitionController.handleAppTransitionReady();
- verify(activity1).commitVisibility(eq(false), anyBoolean(), anyBoolean());
- verify(activity1).applyAnimation(any(), eq(TRANSIT_OLD_ACTIVITY_OPEN), eq(false),
- anyBoolean(), any());
- verify(activity2).commitVisibility(eq(true), anyBoolean(), anyBoolean());
- verify(activity2).applyAnimation(any(), eq(TRANSIT_OLD_ACTIVITY_OPEN), eq(true),
- anyBoolean(), any());
- verify(homeActivity).commitVisibility(eq(true), anyBoolean(), anyBoolean());
- verify(homeActivity, never()).applyAnimation(any(), anyInt(), anyBoolean(), anyBoolean(),
- any());
+ verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
+ verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index bc319db..e3b389b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -98,12 +98,19 @@
@Test
public void backTypeCrossTaskWhenBackToPreviousTask() {
Task taskA = createTask(mDefaultDisplay);
- createActivityRecord(taskA);
+ ActivityRecord recordA = createActivityRecord(taskA);
+ Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+
withSystemCallback(createTopTaskWithActivity());
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
+
+ // verify if back animation would start.
+ verify(mBackNavigationController).scheduleAnimationLocked(
+ eq(BackNavigationInfo.TYPE_CROSS_TASK), any(), eq(mBackAnimationAdapter),
+ any());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index ef84a4b..e85b574 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -24,7 +24,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -139,34 +138,12 @@
}
@Test
- public void testDimAboveNoChildCreatesSurface() {
- final float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- SurfaceControl dimLayer = getDimLayer();
-
- assertNotNull("Dimmer should have created a surface", dimLayer);
-
- verify(mTransaction).setAlpha(dimLayer, alpha);
- verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE);
- }
-
- @Test
- public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() {
- float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
- final SurfaceControl firstSurface = getDimLayer();
-
- alpha = 0.9f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- assertEquals(firstSurface, getDimLayer());
- verify(mTransaction).setAlpha(firstSurface, 0.9f);
- }
-
- @Test
public void testUpdateDimsAppliesCrop() {
- mDimmer.dimAbove(mTransaction, 0.8f);
+ TestWindowContainer child = new TestWindowContainer(mWm);
+ mHost.addChild(child, 0);
+
+ final float alpha = 0.8f;
+ mDimmer.dimAbove(mTransaction, child, alpha);
int width = 100;
int height = 300;
@@ -178,17 +155,6 @@
}
@Test
- public void testDimAboveNoChildNotReset() {
- mDimmer.dimAbove(mTransaction, 0.8f);
- SurfaceControl dimLayer = getDimLayer();
- mDimmer.resetDimStates();
-
- mDimmer.updateDims(mTransaction, new Rect());
- verify(mTransaction).show(getDimLayer());
- verify(mTransaction, never()).remove(dimLayer);
- }
-
- @Test
public void testDimAboveWithChildCreatesSurfaceAboveChild() {
TestWindowContainer child = new TestWindowContainer(mWm);
mHost.addChild(child, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index b1acae2..eab2e15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -42,7 +42,6 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.List;
/**
* Build/Install/Run:
@@ -66,55 +65,6 @@
}
@Test
- public void testCollectTasksByLastActiveTime() {
- // Create a number of stacks with tasks (of incrementing active time)
- final ArrayList<DisplayContent> displays = new ArrayList<>();
- final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
- displays.add(display);
-
- final int numStacks = 2;
- for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
- final Task stack = new TaskBuilder(mSupervisor)
- .setDisplay(display)
- .setOnTop(false)
- .build();
- }
-
- final int numTasks = 10;
- int activeTime = 0;
- final List<Task> rootTasks = new ArrayList<>();
- display.getDefaultTaskDisplayArea().forAllRootTasks(task -> {
- rootTasks.add(task);
- }, false /* traverseTopToBottom */);
- for (int i = 0; i < numTasks; i++) {
- final Task task =
- createTask(rootTasks.get(i % numStacks), ".Task" + i, i, activeTime++, null);
- doReturn(false).when(task).isVisible();
- }
-
- // Ensure that the latest tasks were returned in order of decreasing last active time,
- // collected from all tasks across all the stacks
- final int numFetchTasks = 5;
- ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
- mRunningTasks.getTasks(5, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
- mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
- assertThat(tasks).hasSize(numFetchTasks);
- for (int i = 0; i < numFetchTasks; i++) {
- assertEquals(numTasks - i - 1, tasks.get(i).id);
- }
-
- // Ensure that requesting more than the total number of tasks only returns the subset
- // and does not crash
- tasks.clear();
- mRunningTasks.getTasks(100, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
- mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
- assertThat(tasks).hasSize(numTasks);
- for (int i = 0; i < numTasks; i++) {
- assertEquals(numTasks - i - 1, tasks.get(i).id);
- }
- }
-
- @Test
public void testTaskInfo_expectNoExtrasByDefault() {
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
final int numTasks = 10;
@@ -125,7 +75,7 @@
.build();
final Bundle data = new Bundle();
data.putInt("key", 100);
- createTask(stack, ".Task" + i, i, i, data);
+ createTask(stack, ".Task" + i, i, data);
}
final int numFetchTasks = 5;
@@ -150,7 +100,7 @@
.build();
final Bundle data = new Bundle();
data.putInt("key", 100);
- createTask(stack, ".Task" + i, i, i, data);
+ createTask(stack, ".Task" + i, i, data);
}
final int numFetchTasks = 5;
@@ -167,46 +117,63 @@
}
@Test
- public void testUpdateLastActiveTimeOfVisibleTasks() {
+ public void testGetTasksSortByFocusAndVisibility() {
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
+ final Task stack = new TaskBuilder(mSupervisor)
+ .setDisplay(display)
+ .setOnTop(true)
+ .build();
+
final int numTasks = 10;
final ArrayList<Task> tasks = new ArrayList<>();
for (int i = 0; i < numTasks; i++) {
- final Task task = createTask(null, ".Task" + i, i, i, null);
+ final Task task = createTask(stack, ".Task" + i, i, null);
doReturn(false).when(task).isVisible();
tasks.add(task);
}
- final Task visibleTask = tasks.get(0);
- doReturn(true).when(visibleTask).isVisible();
-
- final Task focusedTask = tasks.get(1);
+ final Task focusedTask = tasks.get(numTasks - 1);
doReturn(true).when(focusedTask).isVisible();
- doReturn(true).when(focusedTask).isFocused();
+ display.mFocusedApp = focusedTask.getTopNonFinishingActivity();
- // Ensure that the last active time of visible tasks were updated while the focused one had
- // the largest last active time.
+ final Task visibleTaskTop = tasks.get(numTasks - 2);
+ doReturn(true).when(visibleTaskTop).isVisible();
+
+ final Task visibleTaskBottom = tasks.get(numTasks - 3);
+ doReturn(true).when(visibleTaskBottom).isVisible();
+
+ // Ensure that the focused Task is on top, visible tasks below, then invisible tasks.
final int numFetchTasks = 5;
final ArrayList<RunningTaskInfo> fetchTasks = new ArrayList<>();
mRunningTasks.getTasks(numFetchTasks, fetchTasks,
FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
assertThat(fetchTasks).hasSize(numFetchTasks);
- assertEquals(fetchTasks.get(0).id, focusedTask.mTaskId);
- assertEquals(fetchTasks.get(1).id, visibleTask.mTaskId);
+ for (int i = 0; i < numFetchTasks; i++) {
+ assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+ }
+
+ // Ensure that requesting more than the total number of tasks only returns the subset
+ // and does not crash
+ fetchTasks.clear();
+ mRunningTasks.getTasks(100, fetchTasks,
+ FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
+ mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
+ assertThat(fetchTasks).hasSize(numTasks);
+ for (int i = 0; i < numTasks; i++) {
+ assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+ }
}
/**
- * Create a task with a single activity in it, with the given last active time.
+ * Create a task with a single activity in it.
*/
- private Task createTask(Task stack, String className, int taskId,
- int lastActiveTime, Bundle extras) {
+ private Task createTask(Task stack, String className, int taskId, Bundle extras) {
final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
.setComponent(new ComponentName(mContext.getPackageName(), className))
.setTaskId(taskId)
.setParentTaskFragment(stack)
.build();
- task.lastActiveTime = lastActiveTime;
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setTask(task)
.setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
@@ -227,7 +194,7 @@
.setDisplay(i % 2 == 0 ? display0 : display1)
.setOnTop(true)
.build();
- final Task task = createTask(stack, ".Task" + i, i, i, null);
+ final Task task = createTask(stack, ".Task" + i, i, null);
tasks.add(task);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index d59fce0..c906abc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -87,7 +87,6 @@
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.InsetsFrameProvider;
-import android.view.InsetsVisibilities;
import android.view.WindowManager;
import androidx.test.filters.MediumTest;
@@ -2302,8 +2301,7 @@
// We should get a null LetterboxDetails object as there is no letterboxed activity, so
// nothing will get passed to SysUI
verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
- any(), anyBoolean(), anyInt(),
- any(InsetsVisibilities.class), isNull(), isNull());
+ any(), anyBoolean(), anyInt(), anyInt(), isNull(), isNull());
}
@@ -2331,8 +2329,7 @@
// Check that letterboxDetails actually gets passed to SysUI
StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
- any(), anyBoolean(), anyInt(),
- any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
}
@Test
@@ -2367,8 +2364,7 @@
// Check that letterboxDetails actually gets passed to SysUI
StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal();
verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(),
- any(), anyBoolean(), anyInt(),
- any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
}
@Test
@@ -2420,8 +2416,7 @@
// Check that letterboxDetails actually gets passed to SysUI
StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
- any(), anyBoolean(), anyInt(),
- any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+ any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
}
private void recomputeNaturalConfigurationOfUnresizableActivity() {
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index dc695d6..e19117b 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -730,6 +730,25 @@
}
/**
+ * Result code to string
+ *
+ * @param result The result code.
+ * @return The result code in string format.
+ *
+ * @hide
+ */
+ public static String resultToString(@Result int result) {
+ switch (result) {
+ case RESULT_OK: return "OK";
+ case RESULT_MUST_DEACTIVATE_SIM : return "MUST_DEACTIVATE_SIM";
+ case RESULT_RESOLVABLE_ERRORS: return "RESOLVABLE_ERRORS";
+ case RESULT_FIRST_USER: return "FIRST_USER";
+ default:
+ return "UNKNOWN(" + result + ")";
+ }
+ }
+
+ /**
* Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
*/
private class IEuiccServiceWrapper extends IEuiccService.Stub {
diff --git a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
index 9add38e..46a049c 100644
--- a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
+++ b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
@@ -123,4 +123,16 @@
public int describeContents() {
return 0;
}
+
+ /**
+ * @hide
+ *
+ * @return String representation of {@link GetEuiccProfileInfoListResult}
+ */
+ @Override
+ public String toString() {
+ return "[GetEuiccProfileInfoListResult: result=" + EuiccService.resultToString(result)
+ + ", isRemovable=" + mIsRemovable + ", mProfiles=" + Arrays.toString(mProfiles)
+ + "]";
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/DomainSelectionService.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/DomainSelectionService.aidl
index 1550ab3..b9d2ba8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/DomainSelectionService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,9 +11,9 @@
* 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.
+ * limitations under the License
*/
-package com.android.systemui.shared.system;
+package android.telephony;
-parcelable RemoteTransitionCompat;
+parcelable DomainSelectionService.SelectionAttributes;
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
new file mode 100644
index 0000000..383561a
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.Annotation.PreciseDisconnectCauses;
+import android.telephony.ims.ImsReasonInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IDomainSelectionServiceController;
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Main domain selection implementation for various telephony features.
+ *
+ * The telephony framework will bind to the {@link DomainSelectionService}.
+ *
+ * @hide
+ */
+public class DomainSelectionService extends Service {
+
+ private static final String LOG_TAG = "DomainSelectionService";
+
+ /**
+ * The intent that must be defined as an intent-filter in the AndroidManifest of the
+ * {@link DomainSelectionService}.
+ *
+ * @hide
+ */
+ public static final String SERVICE_INTERFACE = "android.telephony.DomainSelectionService";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SELECTOR_TYPE_",
+ value = {
+ SELECTOR_TYPE_CALLING,
+ SELECTOR_TYPE_SMS,
+ SELECTOR_TYPE_UT})
+ public @interface SelectorType {}
+
+ /** Indicates the domain selector type for calling. */
+ public static final int SELECTOR_TYPE_CALLING = 1;
+ /** Indicates the domain selector type for sms. */
+ public static final int SELECTOR_TYPE_SMS = 2;
+ /** Indicates the domain selector type for supplementary services. */
+ public static final int SELECTOR_TYPE_UT = 3;
+
+ /** Indicates that the modem can scan for emergency service as per modem’s implementation. */
+ public static final int SCAN_TYPE_NO_PREFERENCE = 0;
+
+ /** Indicates that the modem will scan for emergency service in limited service mode. */
+ public static final int SCAN_TYPE_LIMITED_SERVICE = 1;
+
+ /** Indicates that the modem will scan for emergency service in full service mode. */
+ public static final int SCAN_TYPE_FULL_SERVICE = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SCAN_TYPE_",
+ value = {
+ SCAN_TYPE_NO_PREFERENCE,
+ SCAN_TYPE_LIMITED_SERVICE,
+ SCAN_TYPE_FULL_SERVICE})
+ public @interface EmergencyScanType {}
+
+ /**
+ * Contains attributes required to determine the domain for a telephony service.
+ */
+ public static final class SelectionAttributes implements Parcelable {
+
+ private static final String TAG = "SelectionAttributes";
+
+ private int mSlotId;
+ private int mSubId;
+ private @Nullable String mCallId;
+ private @Nullable String mNumber;
+ private @SelectorType int mSelectorType;
+ private boolean mIsVideoCall;
+ private boolean mIsEmergency;
+ private boolean mIsExitedFromAirplaneMode;
+ //private @Nullable UtAttributes mUtAttributes;
+ private @Nullable ImsReasonInfo mImsReasonInfo;
+ private @PreciseDisconnectCauses int mCause;
+ private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+ /**
+ * @param slotId The slot identifier.
+ * @param subId The subscription identifier.
+ * @param callId The call identifier.
+ * @param number The dialed number.
+ * @param selectorType Indicates the requested domain selector type.
+ * @param video Indicates it's a video call.
+ * @param emergency Indicates it's emergency service.
+ * @param exited {@code true} if the request caused the device to move out of airplane mode.
+ * @param imsReasonInfo The reason why the last PS attempt failed.
+ * @param cause The reason why the last CS attempt failed.
+ * @param regResult The current registration result for emergency services.
+ */
+ private SelectionAttributes(int slotId, int subId, @Nullable String callId,
+ @Nullable String number, @SelectorType int selectorType,
+ boolean video, boolean emergency, boolean exited,
+ /*UtAttributes attr,*/
+ @Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
+ @Nullable EmergencyRegResult regResult) {
+ mSlotId = slotId;
+ mSubId = subId;
+ mCallId = callId;
+ mNumber = number;
+ mSelectorType = selectorType;
+ mIsVideoCall = video;
+ mIsEmergency = emergency;
+ mIsExitedFromAirplaneMode = exited;
+ //mUtAttributes = attr;
+ mImsReasonInfo = imsReasonInfo;
+ mCause = cause;
+ mEmergencyRegResult = regResult;
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param s Source selection attributes.
+ * @hide
+ */
+ public SelectionAttributes(@NonNull SelectionAttributes s) {
+ mSlotId = s.mSlotId;
+ mSubId = s.mSubId;
+ mCallId = s.mCallId;
+ mNumber = s.mNumber;
+ mSelectorType = s.mSelectorType;
+ mIsEmergency = s.mIsEmergency;
+ mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
+ //mUtAttributes = s.mUtAttributes;
+ mImsReasonInfo = s.mImsReasonInfo;
+ mCause = s.mCause;
+ mEmergencyRegResult = s.mEmergencyRegResult;
+ }
+
+ /**
+ * Constructs a SelectionAttributes object from the given parcel.
+ */
+ private SelectionAttributes(@NonNull Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * @return The slot identifier.
+ */
+ public int getSlotId() {
+ return mSlotId;
+ }
+
+ /**
+ * @return The subscription identifier.
+ */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ /**
+ * @return The call identifier.
+ */
+ public @Nullable String getCallId() {
+ return mCallId;
+ }
+
+ /**
+ * @return The dialed number.
+ */
+ public @Nullable String getNumber() {
+ return mNumber;
+ }
+
+ /**
+ * @return The domain selector type.
+ */
+ public @SelectorType int getSelectorType() {
+ return mSelectorType;
+ }
+
+ /**
+ * @return {@code true} if the request is for a video call.
+ */
+ public boolean isVideoCall() {
+ return mIsVideoCall;
+ }
+
+ /**
+ * @return {@code true} if the request is for emergency services.
+ */
+ public boolean isEmergency() {
+ return mIsEmergency;
+ }
+
+ /**
+ * @return {@code true} if the request caused the device to move out of airplane mode.
+ */
+ public boolean isExitedFromAirplaneMode() {
+ return mIsExitedFromAirplaneMode;
+ }
+
+ /*
+ public @Nullable UtAttributes getUtAttributes();
+ return mUtAttributes;
+ }
+ */
+
+ /**
+ * @return The PS disconnect cause if trying over PS resulted in a failure and
+ * reselection is required.
+ */
+ public @Nullable ImsReasonInfo getPsDisconnectCause() {
+ return mImsReasonInfo;
+ }
+
+ /**
+ * @return The CS disconnect cause if trying over CS resulted in a failure and
+ * reselection is required.
+ */
+ public @PreciseDisconnectCauses int getCsDisconnectCause() {
+ return mCause;
+ }
+
+ /**
+ * @return The current registration state of cellular network.
+ */
+ public @Nullable EmergencyRegResult getEmergencyRegResult() {
+ return mEmergencyRegResult;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "{ slotId=" + mSlotId
+ + ", subId=" + mSubId
+ + ", callId=" + mCallId
+ + ", number=" + (Build.IS_DEBUGGABLE ? mNumber : "***")
+ + ", type=" + mSelectorType
+ + ", videoCall=" + mIsVideoCall
+ + ", emergency=" + mIsEmergency
+ + ", airplaneMode=" + mIsExitedFromAirplaneMode
+ + ", reasonInfo=" + mImsReasonInfo
+ + ", cause=" + mCause
+ + ", regResult=" + mEmergencyRegResult
+ + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SelectionAttributes that = (SelectionAttributes) o;
+ return mSlotId == that.mSlotId && mSubId == that.mSubId
+ && TextUtils.equals(mCallId, that.mCallId)
+ && TextUtils.equals(mNumber, that.mNumber)
+ && mSelectorType == that.mSelectorType && mIsVideoCall == that.mIsVideoCall
+ && mIsEmergency == that.mIsEmergency
+ && mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
+ //&& equalsHandlesNulls(mUtAttributes, that.mUtAttributes)
+ && equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
+ && mCause == that.mCause
+ && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCallId, mNumber, mImsReasonInfo,
+ mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, mEmergencyRegResult,
+ mSlotId, mSubId, mSelectorType, mCause);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mSlotId);
+ out.writeInt(mSubId);
+ out.writeString8(mCallId);
+ out.writeString8(mNumber);
+ out.writeInt(mSelectorType);
+ out.writeBoolean(mIsVideoCall);
+ out.writeBoolean(mIsEmergency);
+ out.writeBoolean(mIsExitedFromAirplaneMode);
+ //out.writeParcelable(mUtAttributes, 0);
+ out.writeParcelable(mImsReasonInfo, 0);
+ out.writeInt(mCause);
+ out.writeParcelable(mEmergencyRegResult, 0);
+ }
+
+ private void readFromParcel(@NonNull Parcel in) {
+ mSlotId = in.readInt();
+ mSubId = in.readInt();
+ mCallId = in.readString8();
+ mNumber = in.readString8();
+ mSelectorType = in.readInt();
+ mIsVideoCall = in.readBoolean();
+ mIsEmergency = in.readBoolean();
+ mIsExitedFromAirplaneMode = in.readBoolean();
+ //mUtAttributes = s.mUtAttributes;
+ mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
+ android.telephony.ims.ImsReasonInfo.class);
+ mCause = in.readInt();
+ mEmergencyRegResult = in.readParcelable(EmergencyRegResult.class.getClassLoader(),
+ EmergencyRegResult.class);
+ }
+
+ public static final @NonNull Creator<SelectionAttributes> CREATOR =
+ new Creator<SelectionAttributes>() {
+ @Override
+ public SelectionAttributes createFromParcel(@NonNull Parcel in) {
+ return new SelectionAttributes(in);
+ }
+
+ @Override
+ public SelectionAttributes[] newArray(int size) {
+ return new SelectionAttributes[size];
+ }
+ };
+
+ private static boolean equalsHandlesNulls(Object a, Object b) {
+ return (a == null) ? (b == null) : a.equals(b);
+ }
+
+ /**
+ * Builder class creating a new instance.
+ */
+ public static final class Builder {
+ private final int mSlotId;
+ private final int mSubId;
+ private @Nullable String mCallId;
+ private @Nullable String mNumber;
+ private final @SelectorType int mSelectorType;
+ private boolean mIsVideoCall;
+ private boolean mIsEmergency;
+ private boolean mIsExitedFromAirplaneMode;
+ //private @Nullable UtAttributes mUtAttributes;
+ private @Nullable ImsReasonInfo mImsReasonInfo;
+ private @PreciseDisconnectCauses int mCause;
+ private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+ /**
+ * Default constructor for Builder.
+ */
+ public Builder(int slotId, int subId, @SelectorType int selectorType) {
+ mSlotId = slotId;
+ mSubId = subId;
+ mSelectorType = selectorType;
+ }
+
+ /**
+ * Sets the call identifier.
+ *
+ * @param callId The call identifier.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setCallId(@NonNull String callId) {
+ mCallId = callId;
+ return this;
+ }
+
+ /**
+ * Sets the dialed number.
+ *
+ * @param number The dialed number.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setNumber(@NonNull String number) {
+ mNumber = number;
+ return this;
+ }
+
+ /**
+ * Sets whether it's a video call or not.
+ *
+ * @param video Indicates it's a video call.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setVideoCall(boolean video) {
+ mIsVideoCall = video;
+ return this;
+ }
+
+ /**
+ * Sets whether it's an emergency service or not.
+ *
+ * @param emergency Indicates it's emergency service.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setEmergency(boolean emergency) {
+ mIsEmergency = emergency;
+ return this;
+ }
+
+ /**
+ * Sets whether the request caused the device to move out of airplane mode.
+ *
+ * @param exited {@code true} if the request caused the device to move out of
+ * airplane mode.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setExitedFromAirplaneMode(boolean exited) {
+ mIsExitedFromAirplaneMode = exited;
+ return this;
+ }
+
+ /**
+ * Sets the Ut service attributes.
+ * Only applicable for SELECTOR_TYPE_UT
+ *
+ * @param attr Ut services attributes.
+ * @return The same instance of the builder.
+ */
+ /*
+ public @NonNull Builder setUtAttributes(@NonNull UtAttributes attr);
+ mUtAttributes = attr;
+ return this;
+ }
+ */
+
+ /**
+ * Sets an optional reason why the last PS attempt failed.
+ *
+ * @param info The reason why the last PS attempt failed.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setPsDisconnectCause(@NonNull ImsReasonInfo info) {
+ mImsReasonInfo = info;
+ return this;
+ }
+
+ /**
+ * Sets an optional reason why the last CS attempt failed.
+ *
+ * @param cause The reason why the last CS attempt failed.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setCsDisconnectCause(@PreciseDisconnectCauses int cause) {
+ mCause = cause;
+ return this;
+ }
+
+ /**
+ * Sets the current registration result for emergency services.
+ *
+ * @param regResult The current registration result for emergency services.
+ * @return The same instance of the builder.
+ */
+ public @NonNull Builder setEmergencyRegResult(@NonNull EmergencyRegResult regResult) {
+ mEmergencyRegResult = regResult;
+ return this;
+ }
+
+ /**
+ * Build the SelectionAttributes.
+ * @return The SelectionAttributes object.
+ */
+ public @NonNull SelectionAttributes build() {
+ return new SelectionAttributes(mSlotId, mSubId, mCallId, mNumber, mSelectorType,
+ mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, /*mUtAttributes,*/
+ mImsReasonInfo, mCause, mEmergencyRegResult);
+ }
+ }
+ }
+
+ /**
+ * A wrapper class for ITransportSelectorCallback interface.
+ */
+ private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
+ private static final String TAG = "TransportSelectorCallbackWrapper";
+
+ private final @NonNull ITransportSelectorCallback mCallback;
+ private final @NonNull Executor mExecutor;
+
+ private @Nullable ITransportSelectorResultCallbackAdapter mResultCallback;
+ private @Nullable DomainSelectorWrapper mSelectorWrapper;
+
+ TransportSelectorCallbackWrapper(@NonNull ITransportSelectorCallback cb,
+ @NonNull Executor executor) {
+ mCallback = cb;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCreated(@NonNull DomainSelector selector) {
+ try {
+ mSelectorWrapper = new DomainSelectorWrapper(selector, mExecutor);
+ mCallback.onCreated(mSelectorWrapper.getCallbackBinder());
+ } catch (Exception e) {
+ Rlog.e(TAG, "onCreated e=" + e);
+ }
+ }
+
+ @Override
+ public void onWlanSelected() {
+ try {
+ mCallback.onWlanSelected();
+ } catch (Exception e) {
+ Rlog.e(TAG, "onWlanSelected e=" + e);
+ }
+ }
+
+ @Override
+ public @NonNull WwanSelectorCallback onWwanSelected() {
+ WwanSelectorCallback callback = null;
+ try {
+ IWwanSelectorCallback cb = mCallback.onWwanSelected();
+ callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+ } catch (Exception e) {
+ Rlog.e(TAG, "onWwanSelected e=" + e);
+ }
+
+ return callback;
+ }
+
+ @Override
+ public void onWwanSelected(Consumer<WwanSelectorCallback> consumer) {
+ try {
+ mResultCallback = new ITransportSelectorResultCallbackAdapter(consumer, mExecutor);
+ mCallback.onWwanSelectedAsync(mResultCallback);
+ } catch (Exception e) {
+ Rlog.e(TAG, "onWwanSelected e=" + e);
+ executeMethodAsyncNoException(mExecutor,
+ () -> consumer.accept(null), TAG, "onWwanSelectedAsync-Exception");
+ }
+ }
+
+ @Override
+ public void onSelectionTerminated(@DisconnectCauses int cause) {
+ try {
+ mCallback.onSelectionTerminated(cause);
+ mSelectorWrapper = null;
+ } catch (Exception e) {
+ Rlog.e(TAG, "onSelectionTerminated e=" + e);
+ }
+ }
+
+ private class ITransportSelectorResultCallbackAdapter
+ extends ITransportSelectorResultCallback.Stub {
+ private final @NonNull Consumer<WwanSelectorCallback> mConsumer;
+ private final @NonNull Executor mExecutor;
+
+ ITransportSelectorResultCallbackAdapter(
+ @NonNull Consumer<WwanSelectorCallback> consumer,
+ @NonNull Executor executor) {
+ mConsumer = consumer;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCompleted(@NonNull IWwanSelectorCallback cb) {
+ if (mConsumer == null) return;
+
+ WwanSelectorCallback callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+ executeMethodAsyncNoException(mExecutor,
+ () -> mConsumer.accept(callback), TAG, "onWwanSelectedAsync-Completed");
+ }
+ }
+ }
+
+ /**
+ * A wrapper class for IDomainSelector interface.
+ */
+ private final class DomainSelectorWrapper {
+ private static final String TAG = "DomainSelectorWrapper";
+
+ private @NonNull IDomainSelector mCallbackBinder;
+
+ DomainSelectorWrapper(@NonNull DomainSelector cb, @NonNull Executor executor) {
+ mCallbackBinder = new IDomainSelectorAdapter(cb, executor);
+ }
+
+ private class IDomainSelectorAdapter extends IDomainSelector.Stub {
+ private final @NonNull WeakReference<DomainSelector> mDomainSelectorWeakRef;
+ private final @NonNull Executor mExecutor;
+
+ IDomainSelectorAdapter(@NonNull DomainSelector domainSelector,
+ @NonNull Executor executor) {
+ mDomainSelectorWeakRef =
+ new WeakReference<DomainSelector>(domainSelector);
+ mExecutor = executor;
+ }
+
+ @Override
+ public void cancelSelection() {
+ final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+ if (domainSelector == null) return;
+
+ executeMethodAsyncNoException(mExecutor,
+ () -> domainSelector.cancelSelection(), TAG, "cancelSelection");
+ }
+
+ @Override
+ public void reselectDomain(@NonNull SelectionAttributes attr) {
+ final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+ if (domainSelector == null) return;
+
+ executeMethodAsyncNoException(mExecutor,
+ () -> domainSelector.reselectDomain(attr), TAG, "reselectDomain");
+ }
+
+ @Override
+ public void finishSelection() {
+ final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+ if (domainSelector == null) return;
+
+ executeMethodAsyncNoException(mExecutor,
+ () -> domainSelector.finishSelection(), TAG, "finishSelection");
+ }
+ }
+
+ public @NonNull IDomainSelector getCallbackBinder() {
+ return mCallbackBinder;
+ }
+ }
+
+ /**
+ * A wrapper class for IWwanSelectorCallback and IWwanSelectorResultCallback.
+ */
+ private final class WwanSelectorCallbackWrapper
+ implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
+ private static final String TAG = "WwanSelectorCallbackWrapper";
+
+ private final @NonNull IWwanSelectorCallback mCallback;
+ private final @NonNull Executor mExecutor;
+
+ private @Nullable IWwanSelectorResultCallbackAdapter mResultCallback;
+
+ WwanSelectorCallbackWrapper(@NonNull IWwanSelectorCallback cb,
+ @NonNull Executor executor) {
+ mCallback = cb;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onCancel() {
+ try {
+ mCallback.onCancel();
+ } catch (Exception e) {
+ Rlog.e(TAG, "onCancel e=" + e);
+ }
+ }
+
+ @Override
+ public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+ @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
+ @NonNull Consumer<EmergencyRegResult> consumer) {
+ try {
+ if (signal != null) signal.setOnCancelListener(this);
+ mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
+ mCallback.onRequestEmergencyNetworkScan(
+ preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
+ scanType, mResultCallback);
+ } catch (Exception e) {
+ Rlog.e(TAG, "onRequestEmergencyNetworkScan e=" + e);
+ }
+ }
+
+ @Override
+ public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) {
+ try {
+ mCallback.onDomainSelected(accessNetworkType);
+ } catch (Exception e) {
+ Rlog.e(TAG, "onDomainSelected e=" + e);
+ }
+ }
+
+ private class IWwanSelectorResultCallbackAdapter
+ extends IWwanSelectorResultCallback.Stub {
+ private final @NonNull Consumer<EmergencyRegResult> mConsumer;
+ private final @NonNull Executor mExecutor;
+
+ IWwanSelectorResultCallbackAdapter(@NonNull Consumer<EmergencyRegResult> consumer,
+ @NonNull Executor executor) {
+ mConsumer = consumer;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void onComplete(@NonNull EmergencyRegResult result) {
+ if (mConsumer == null) return;
+
+ executeMethodAsyncNoException(mExecutor,
+ () -> mConsumer.accept(result), TAG, "onScanComplete");
+ }
+ }
+ }
+
+ private final Object mExecutorLock = new Object();
+
+ /** Executor used to execute methods called remotely by the framework. */
+ private @NonNull Executor mExecutor;
+
+ /**
+ * Selects a domain for the given operation.
+ *
+ * @param attr Required to determine the domain.
+ * @param callback The callback instance being registered.
+ */
+ public void onDomainSelection(@NonNull SelectionAttributes attr,
+ @NonNull TransportSelectorCallback callback) {
+ }
+
+ /**
+ * Notifies the change in {@link ServiceState} for a specific slot.
+ *
+ * @param slotId For which the state changed.
+ * @param subId For which the state changed.
+ * @param serviceState Updated {@link ServiceState}.
+ */
+ public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
+ }
+
+ /**
+ * Notifies the change in {@link BarringInfo} for a specific slot.
+ *
+ * @param slotId For which the state changed.
+ * @param subId For which the state changed.
+ * @param info Updated {@link BarringInfo}.
+ */
+ public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo info) {
+ }
+
+ private final IBinder mDomainSelectionServiceController =
+ new IDomainSelectionServiceController.Stub() {
+ @Override
+ public void selectDomain(@NonNull SelectionAttributes attr,
+ @NonNull ITransportSelectorCallback callback) throws RemoteException {
+ executeMethodAsync(getCachedExecutor(),
+ () -> DomainSelectionService.this.onDomainSelection(attr,
+ new TransportSelectorCallbackWrapper(callback, getCachedExecutor())),
+ LOG_TAG, "onDomainSelection");
+ }
+
+ @Override
+ public void updateServiceState(int slotId, int subId, @NonNull ServiceState serviceState) {
+ executeMethodAsyncNoException(getCachedExecutor(),
+ () -> DomainSelectionService.this.onServiceStateUpdated(slotId,
+ subId, serviceState), LOG_TAG, "onServiceStateUpdated");
+ }
+
+ @Override
+ public void updateBarringInfo(int slotId, int subId, @NonNull BarringInfo info) {
+ executeMethodAsyncNoException(getCachedExecutor(),
+ () -> DomainSelectionService.this.onBarringInfoUpdated(slotId, subId, info),
+ LOG_TAG, "onBarringInfoUpdated");
+ }
+ };
+
+ private static void executeMethodAsync(@NonNull Executor executor, @NonNull Runnable r,
+ @NonNull String tag, @NonNull String errorLogName) throws RemoteException {
+ try {
+ CompletableFuture.runAsync(
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+ } catch (CancellationException | CompletionException e) {
+ Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ private void executeMethodAsyncNoException(@NonNull Executor executor, @NonNull Runnable r,
+ @NonNull String tag, @NonNull String errorLogName) {
+ try {
+ CompletableFuture.runAsync(
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+ } catch (CancellationException | CompletionException e) {
+ Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+ }
+ }
+
+ /** @hide */
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ Log.i(LOG_TAG, "DomainSelectionService Bound.");
+ return mDomainSelectionServiceController;
+ }
+ return null;
+ }
+
+ /**
+ * The DomainSelectionService will be able to define an {@link Executor} that the service
+ * can use to execute the methods. It has set the default executor as Runnable::run,
+ *
+ * @return An {@link Executor} to be used.
+ */
+ @SuppressLint("OnNameExpected")
+ public @NonNull Executor getExecutor() {
+ return Runnable::run;
+ }
+
+ private @NonNull Executor getCachedExecutor() {
+ synchronized (mExecutorLock) {
+ if (mExecutor == null) {
+ Executor e = getExecutor();
+ mExecutor = (e != null) ? e : Runnable::run;
+ }
+ return mExecutor;
+ }
+ }
+
+ /**
+ * Returns a string representation of the domain.
+ * @param domain The domain.
+ * @return The name of the domain.
+ * @hide
+ */
+ public static @NonNull String getDomainName(@NetworkRegistrationInfo.Domain int domain) {
+ return NetworkRegistrationInfo.domainToString(domain);
+ }
+}
diff --git a/telephony/java/android/telephony/DomainSelector.java b/telephony/java/android/telephony/DomainSelector.java
new file mode 100644
index 0000000..0871831
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+
+/**
+ * Implemented as part of the {@link DomainSelectionService} to implement domain selection
+ * for a specific use case.
+ * @hide
+ */
+public interface DomainSelector {
+ /**
+ * Cancel an ongoing selection operation. It is up to the DomainSelectionService
+ * to clean up all ongoing operations with the framework.
+ */
+ void cancelSelection();
+
+ /**
+ * Reselect a domain due to the call not setting up properly.
+ *
+ * @param attr attributes required to select the domain.
+ */
+ void reselectDomain(@NonNull SelectionAttributes attr);
+
+ /**
+ * Finish the selection procedure and clean everything up.
+ */
+ void finishSelection();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/EmergencyRegResult.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/EmergencyRegResult.aidl
index 1550ab3..f722962 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/EmergencyRegResult.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,9 +11,9 @@
* 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.
+ * limitations under the License
*/
-package com.android.systemui.shared.system;
+package android.telephony;
-parcelable RemoteTransitionCompat;
+parcelable EmergencyRegResult;
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegResult.java
new file mode 100644
index 0000000..5aed412
--- /dev/null
+++ b/telephony/java/android/telephony/EmergencyRegResult.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Contains attributes required to determine the domain for a telephony service
+ * @hide
+ */
+public final class EmergencyRegResult implements Parcelable {
+
+ /**
+ * Indicates the cellular network type of the acquired system.
+ */
+ private @AccessNetworkConstants.RadioAccessNetworkType int mAccessNetworkType;
+
+ /**
+ * Registration state of the acquired system.
+ */
+ private @NetworkRegistrationInfo.RegistrationState int mRegState;
+
+ /**
+ * EMC domain indicates the current domain of the acquired system.
+ */
+ private @NetworkRegistrationInfo.Domain int mDomain;
+
+ /**
+ * Indicates whether the network supports voice over PS network.
+ */
+ private boolean mIsVopsSupported;
+
+ /**
+ * This indicates if camped network support VoLTE emergency bearers.
+ * This should only be set if the UE is in LTE mode.
+ */
+ private boolean mIsEmcBearerSupported;
+
+ /**
+ * The value of the network provided EMC in 5G Registration ACCEPT.
+ * This should be set only if the UE is in 5G mode.
+ */
+ private int mNwProvidedEmc;
+
+ /**
+ * The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+ * This should be set only if the UE is in 5G mode.
+ */
+ private int mNwProvidedEmf;
+
+ /** 3-digit Mobile Country Code, 000..999, empty string if unknown. */
+ private @NonNull String mMcc;
+
+ /** 2 or 3-digit Mobile Network Code, 00..999, empty string if unknown. */
+ private @NonNull String mMnc;
+
+ /**
+ * The ISO-3166-1 alpha-2 country code equivalent for the network's country code,
+ * empty string if unknown.
+ */
+ private @NonNull String mIso;
+
+ /**
+ * Constructor
+ * @param accessNetwork Indicates the network type of the acquired system.
+ * @param regState Indicates the registration state of the acquired system.
+ * @param domain Indicates the current domain of the acquired system.
+ * @param isVopsSupported Indicates whether the network supports voice over PS network.
+ * @param isEmcBearerSupported Indicates if camped network support VoLTE emergency bearers.
+ * @param emc The value of the network provided EMC in 5G Registration ACCEPT.
+ * @param emf The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+ * @param mcc Mobile country code, empty string if unknown.
+ * @param mnc Mobile network code, empty string if unknown.
+ * @param iso The ISO-3166-1 alpha-2 country code equivalent, empty string if unknown.
+ * @hide
+ */
+ public EmergencyRegResult(
+ @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+ @NetworkRegistrationInfo.RegistrationState int regState,
+ @NetworkRegistrationInfo.Domain int domain,
+ boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
+ @NonNull String mcc, @NonNull String mnc, @NonNull String iso) {
+ mAccessNetworkType = accessNetwork;
+ mRegState = regState;
+ mDomain = domain;
+ mIsVopsSupported = isVopsSupported;
+ mIsEmcBearerSupported = isEmcBearerSupported;
+ mNwProvidedEmc = emc;
+ mNwProvidedEmf = emf;
+ mMcc = mcc;
+ mMnc = mnc;
+ mIso = iso;
+ }
+
+ /**
+ * Copy constructors
+ *
+ * @param s Source emergency scan result
+ * @hide
+ */
+ public EmergencyRegResult(@NonNull EmergencyRegResult s) {
+ mAccessNetworkType = s.mAccessNetworkType;
+ mRegState = s.mRegState;
+ mDomain = s.mDomain;
+ mIsVopsSupported = s.mIsVopsSupported;
+ mIsEmcBearerSupported = s.mIsEmcBearerSupported;
+ mNwProvidedEmc = s.mNwProvidedEmc;
+ mNwProvidedEmf = s.mNwProvidedEmf;
+ mMcc = s.mMcc;
+ mMnc = s.mMnc;
+ mIso = s.mIso;
+ }
+
+ /**
+ * Construct a EmergencyRegResult object from the given parcel.
+ */
+ private EmergencyRegResult(@NonNull Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * Returns the cellular access network type of the acquired system.
+ *
+ * @return the cellular network type.
+ */
+ public @AccessNetworkConstants.RadioAccessNetworkType int getAccessNetwork() {
+ return mAccessNetworkType;
+ }
+
+ /**
+ * Returns the registration state of the acquired system.
+ *
+ * @return the registration state.
+ */
+ public @NetworkRegistrationInfo.RegistrationState int getRegState() {
+ return mRegState;
+ }
+
+ /**
+ * Returns the current domain of the acquired system.
+ *
+ * @return the current domain.
+ */
+ public @NetworkRegistrationInfo.Domain int getDomain() {
+ return mDomain;
+ }
+
+ /**
+ * Returns whether the network supports voice over PS network.
+ *
+ * @return {@code true} if the network supports voice over PS network.
+ */
+ public boolean isVopsSupported() {
+ return mIsVopsSupported;
+ }
+
+ /**
+ * Returns whether camped network support VoLTE emergency bearers.
+ * This is not valid if the UE is not in LTE mode.
+ *
+ * @return {@code true} if the network supports VoLTE emergency bearers.
+ */
+ public boolean isEmcBearerSupported() {
+ return mIsEmcBearerSupported;
+ }
+
+ /**
+ * Returns the value of the network provided EMC in 5G Registration ACCEPT.
+ * This is not valid if UE is not in 5G mode.
+ *
+ * @return the value of the network provided EMC.
+ */
+ public int getNwProvidedEmc() {
+ return mNwProvidedEmc;
+ }
+
+ /**
+ * Returns the value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+ * This is not valid if UE is not in 5G mode.
+ *
+ * @return the value of the network provided EMF.
+ */
+ public int getNwProvidedEmf() {
+ return mNwProvidedEmf;
+ }
+
+ /**
+ * Returns 3-digit Mobile Country Code.
+ *
+ * @return Mobile Country Code.
+ */
+ public @NonNull String getMcc() {
+ return mMcc;
+ }
+
+ /**
+ * Returns 2 or 3-digit Mobile Network Code.
+ *
+ * @return Mobile Network Code.
+ */
+ public @NonNull String getMnc() {
+ return mMnc;
+ }
+
+ /**
+ * Returns the ISO-3166-1 alpha-2 country code is provided in lowercase 2 character format.
+ *
+ * @return Country code.
+ */
+ public @NonNull String getIso() {
+ return mIso;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return "{ accessNetwork="
+ + AccessNetworkConstants.AccessNetworkType.toString(mAccessNetworkType)
+ + ", regState=" + NetworkRegistrationInfo.registrationStateToString(mRegState)
+ + ", domain=" + NetworkRegistrationInfo.domainToString(mDomain)
+ + ", vops=" + mIsVopsSupported
+ + ", emcBearer=" + mIsEmcBearerSupported
+ + ", emc=" + mNwProvidedEmc
+ + ", emf=" + mNwProvidedEmf
+ + ", mcc=" + mMcc
+ + ", mnc=" + mMnc
+ + ", iso=" + mIso
+ + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ EmergencyRegResult that = (EmergencyRegResult) o;
+ return mAccessNetworkType == that.mAccessNetworkType
+ && mRegState == that.mRegState
+ && mDomain == that.mDomain
+ && mIsVopsSupported == that.mIsVopsSupported
+ && mIsEmcBearerSupported == that.mIsEmcBearerSupported
+ && mNwProvidedEmc == that.mNwProvidedEmc
+ && mNwProvidedEmf == that.mNwProvidedEmf
+ && TextUtils.equals(mMcc, that.mMcc)
+ && TextUtils.equals(mMnc, that.mMnc)
+ && TextUtils.equals(mIso, that.mIso);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAccessNetworkType, mRegState, mDomain,
+ mIsVopsSupported, mIsEmcBearerSupported,
+ mNwProvidedEmc, mNwProvidedEmf,
+ mMcc, mMnc, mIso);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mAccessNetworkType);
+ out.writeInt(mRegState);
+ out.writeInt(mDomain);
+ out.writeBoolean(mIsVopsSupported);
+ out.writeBoolean(mIsEmcBearerSupported);
+ out.writeInt(mNwProvidedEmc);
+ out.writeInt(mNwProvidedEmf);
+ out.writeString8(mMcc);
+ out.writeString8(mMnc);
+ out.writeString8(mIso);
+ }
+
+ private void readFromParcel(@NonNull Parcel in) {
+ mAccessNetworkType = in.readInt();
+ mRegState = in.readInt();
+ mDomain = in.readInt();
+ mIsVopsSupported = in.readBoolean();
+ mIsEmcBearerSupported = in.readBoolean();
+ mNwProvidedEmc = in.readInt();
+ mNwProvidedEmf = in.readInt();
+ mMcc = in.readString8();
+ mMnc = in.readString8();
+ mIso = in.readString8();
+ }
+
+ public static final @NonNull Creator<EmergencyRegResult> CREATOR =
+ new Creator<EmergencyRegResult>() {
+ @Override
+ public EmergencyRegResult createFromParcel(@NonNull Parcel in) {
+ return new EmergencyRegResult(in);
+ }
+
+ @Override
+ public EmergencyRegResult[] newArray(int size) {
+ return new EmergencyRegResult[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java
new file mode 100644
index 0000000..d396790
--- /dev/null
+++ b/telephony/java/android/telephony/TransportSelectorCallback.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.Annotation.DisconnectCauses;
+
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the transport selection result.
+ * @hide
+ */
+public interface TransportSelectorCallback {
+ /**
+ * Notify that {@link DomainSelector} instance has been created for the selection request.
+ *
+ * @param selector the {@link DomainSelector} instance created.
+ */
+ void onCreated(@NonNull DomainSelector selector);
+
+ /**
+ * Notify that WLAN transport has been selected.
+ */
+ void onWlanSelected();
+
+ /**
+ * Notify that WWAN transport has been selected.
+ */
+ @NonNull WwanSelectorCallback onWwanSelected();
+
+ /**
+ * Notify that WWAN transport has been selected.
+ *
+ * @param consumer The callback to receive the result.
+ */
+ void onWwanSelected(Consumer<WwanSelectorCallback> consumer);
+
+ /**
+ * Notify that selection has terminated because there is no decision that can be made
+ * or a timeout has occurred. The call should be terminated when this method is called.
+ *
+ * @param cause indicates the reason.
+ */
+ void onSelectionTerminated(@DisconnectCauses int cause);
+}
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
new file mode 100644
index 0000000..489a589
--- /dev/null
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.CancellationSignal;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.DomainSelectionService.EmergencyScanType;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the domain selection result.
+ * @hide
+ */
+public interface WwanSelectorCallback {
+ /**
+ * Notify the framework that the {@link DomainSelectionService} has requested an emergency
+ * network scan as part of selection.
+ *
+ * @param preferredNetworks the ordered list of preferred networks to scan.
+ * @param scanType indicates the scan preference, such as full service or limited service.
+ * @param signal notifies when the operation is canceled.
+ * @param consumer the handler of the response.
+ */
+ void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+ @EmergencyScanType int scanType,
+ @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
+
+ /**
+ * Notifies the FW that the domain has been selected. After this method is called,
+ * this interface can be discarded.
+ *
+ * @param accessNetworkType the selected network type.
+ */
+ void onDomainSelected(@RadioAccessNetworkType int accessNetworkType);
+}
diff --git a/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
new file mode 100644
index 0000000..8ad79ed
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.BarringInfo;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.ServiceState;
+
+import com.android.internal.telephony.ITransportSelectorCallback;
+
+oneway interface IDomainSelectionServiceController {
+ void selectDomain(in SelectionAttributes attr, in ITransportSelectorCallback callback);
+ void updateServiceState(int slotId, int subId, in ServiceState serviceState);
+ void updateBarringInfo(int slotId, int subId, in BarringInfo info);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
similarity index 70%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to telephony/java/com/android/internal/telephony/IDomainSelector.aidl
index f79ca10..d94840b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
@@ -14,15 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell.floating;
+package com.android.internal.telephony;
-import android.content.Intent;
+import android.telephony.DomainSelectionService.SelectionAttributes;
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
- void showTask(in Intent intent) = 1;
-
+oneway interface IDomainSelector {
+ void cancelSelection();
+ void reselectDomain(in SelectionAttributes attr);
+ void finishSelection();
}
diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
new file mode 100644
index 0000000..aca83f4
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+
+interface ITransportSelectorCallback {
+ oneway void onCreated(in IDomainSelector selector);
+ oneway void onWlanSelected();
+ IWwanSelectorCallback onWwanSelected();
+ oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
+ oneway void onSelectionTerminated(int cause);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
similarity index 72%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
rename to telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
index f79ca10..1460b45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
@@ -14,15 +14,10 @@
* limitations under the License.
*/
-package com.android.wm.shell.floating;
+package com.android.internal.telephony;
-import android.content.Intent;
+import com.android.internal.telephony.IWwanSelectorCallback;
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
- void showTask(in Intent intent) = 1;
-
+oneway interface ITransportSelectorResultCallback {
+ void onCompleted(in IWwanSelectorCallback cb);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
similarity index 63%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index f79ca10..65d994b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package com.android.wm.shell.floating;
+package com.android.internal.telephony;
-import android.content.Intent;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
- void showTask(in Intent intent) = 1;
-
+oneway interface IWwanSelectorCallback {
+ void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
+ int scanType, in IWwanSelectorResultCallback cb);
+ void onDomainSelected(int accessNetworkType);
+ void onCancel();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
similarity index 72%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
copy to telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
index f79ca10..0d61fcb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
@@ -14,15 +14,10 @@
* limitations under the License.
*/
-package com.android.wm.shell.floating;
+package com.android.internal.telephony;
-import android.content.Intent;
+import android.telephony.EmergencyRegResult;
-/**
- * Interface that is exposed to remote callers to manipulate floating task features.
- */
-interface IFloatingTasks {
-
- void showTask(in Intent intent) = 1;
-
+oneway interface IWwanSelectorResultCallback {
+ void onComplete(in EmergencyRegResult result);
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index b2f7be6..0f83a05 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -239,4 +239,10 @@
public static final int CELL_OFF_FLAG = 0;
public static final int CELL_ON_FLAG = 1;
public static final int CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG = 2;
+
+ /** The key to specify the selected domain for dialing calls. */
+ public static final String EXTRA_DIAL_DOMAIN = "dial_domain";
+
+ /** The key to specify the emergency service category */
+ public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
}
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
index 29d87a2..ec1709c 100644
--- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -25,6 +25,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -98,6 +99,7 @@
}
@Test
+ @Ignore
public void testBadUpdateRollback() throws Exception {
// Need to switch user in order to send broadcasts in device tests
assertTrue(getDevice().switchUser(mSecondaryUserId));
diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py
index 35a0ce6..87b4c68 100755
--- a/tools/fonts/fontchain_linter.py
+++ b/tools/fonts/fontchain_linter.py
@@ -381,62 +381,14 @@
return tuple(f"{s:X}" for s in sequence)
return hex(sequence)
-def check_plausible_compat_pua(coverage, all_emoji, equivalent_emoji):
- # A PUA should point to every RGI emoji and that PUA should be unique to the
- # set of equivalent sequences for the emoji.
- problems = []
- for seq in all_emoji:
- # We're looking to match not-PUA with PUA so filter out existing PUA
- if contains_pua(seq):
- continue
-
- # Filter out non-RGI things that end up in all_emoji
- if only_tags(seq) or seq in {ZWJ, COMBINING_KEYCAP, EMPTY_FLAG_SEQUENCE}:
- continue
-
- equivalents = [seq]
- if seq in equivalent_emoji:
- equivalents.append(equivalent_emoji[seq])
-
- # If there are problems the hex code is much more useful
- log_equivalents = [hex_strs(s) for s in equivalents]
-
- # The system compat font should NOT include regional indicators as these have been split out
- if contains_regional_indicator(seq):
- assert not any(s in coverage for s in equivalents), f"Regional indicators not expected in compat font, found {log_equivalents}"
- continue
-
- glyph = {coverage[e] for e in equivalents}
- if len(glyph) != 1:
- problems.append(f"{log_equivalents} should all point to the same glyph")
- continue
- glyph = next(iter(glyph))
-
- pua = {s for s, g in coverage.items() if contains_pua(s) and g == glyph}
- if not pua:
- problems.append(f"Expected PUA for {log_equivalents} but none exist")
- continue
-
- assert not problems, "\n".join(sorted(problems)) + f"\n{len(problems)} PUA problems"
-
-def check_emoji_compat(all_emoji, equivalent_emoji):
+def check_emoji_not_compat(all_emoji, equivalent_emoji):
compat_psnames = set()
for emoji_font in get_emoji_fonts():
ttf = open_font(emoji_font)
psname = get_psname(ttf)
- is_compat_font = "meta" in ttf and 'Emji' in ttf["meta"].data
- if not is_compat_font:
- continue
- compat_psnames.add(psname)
-
- # If the font has compat metadata it should have PUAs for emoji sequences
- coverage = get_emoji_map(emoji_font)
- check_plausible_compat_pua(coverage, all_emoji, equivalent_emoji)
-
-
- # NotoColorEmoji must be a Compat font.
- assert 'NotoColorEmoji' in compat_psnames, 'NotoColorEmoji MUST be a compat font'
+ if "meta" in ttf:
+ assert 'Emji' not in ttf["meta"].data, 'NotoColorEmoji MUST be a compat font'
def check_emoji_font_coverage(emoji_fonts, all_emoji, equivalent_emoji):
@@ -847,7 +799,7 @@
ucd_path = sys.argv[3]
parse_ucd(ucd_path)
all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji()
- check_emoji_compat(all_emoji, equivalent_emoji)
+ check_emoji_not_compat(all_emoji, equivalent_emoji)
check_emoji_coverage(all_emoji, equivalent_emoji)
check_emoji_defaults(default_emoji)