Merge "Don't show biometric prompt if owner is not in foreground" into udc-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 92fc78e..4f5eb37 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1598,7 +1598,7 @@
uId, null, jobStatus.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
JobProtoEnums.INTERNAL_STOP_REASON_UNKNOWN, jobStatus.getStandbyBucket(),
- jobStatus.getJobId(),
+ jobStatus.getLoggingJobId(),
jobStatus.hasChargingConstraint(),
jobStatus.hasBatteryNotLowConstraint(),
jobStatus.hasStorageNotLowConstraint(),
@@ -2026,7 +2026,7 @@
cancelled.getSourceUid(), null, cancelled.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__CANCELLED,
internalReasonCode, cancelled.getStandbyBucket(),
- cancelled.getJobId(),
+ cancelled.getLoggingJobId(),
cancelled.hasChargingConstraint(),
cancelled.hasBatteryNotLowConstraint(),
cancelled.hasStorageNotLowConstraint(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index bf2e456..90f1523 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -465,7 +465,8 @@
job.getSourceUid(), null, job.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__STARTED,
JobProtoEnums.INTERNAL_STOP_REASON_UNKNOWN,
- job.getStandbyBucket(), job.getJobId(),
+ job.getStandbyBucket(),
+ job.getLoggingJobId(),
job.hasChargingConstraint(),
job.hasBatteryNotLowConstraint(),
job.hasStorageNotLowConstraint(),
@@ -1521,7 +1522,8 @@
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
completedJob.getSourceUid(), null, completedJob.getBatteryName(),
FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
- loggingInternalStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
+ loggingInternalStopReason, completedJob.getStandbyBucket(),
+ completedJob.getLoggingJobId(),
completedJob.hasChargingConstraint(),
completedJob.hasBatteryNotLowConstraint(),
completedJob.hasStorageNotLowConstraint(),
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 b5d763c..a8b4c69 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
@@ -231,6 +231,8 @@
final String sourceTag;
@Nullable
private final String mNamespace;
+ /** An ID that can be used to uniquely identify the job when logging statsd metrics. */
+ private final long mLoggingJobId;
final String tag;
@@ -568,6 +570,7 @@
this.callingUid = callingUid;
this.standbyBucket = standbyBucket;
mNamespace = namespace;
+ mLoggingJobId = generateLoggingId(namespace, job.getId());
int tempSourceUid = -1;
if (sourceUserId != -1 && sourcePackageName != null) {
@@ -804,6 +807,13 @@
/*innerFlags=*/ 0, /* dynamicConstraints */ 0);
}
+ private long generateLoggingId(@Nullable String namespace, int jobId) {
+ if (namespace == null) {
+ return jobId;
+ }
+ return ((long) namespace.hashCode()) << 31 | jobId;
+ }
+
public void enqueueWorkLocked(JobWorkItem work) {
if (pendingWork == null) {
pendingWork = new ArrayList<>();
@@ -956,6 +966,11 @@
return job.getId();
}
+ /** Returns an ID that can be used to uniquely identify the job when logging statsd metrics. */
+ public long getLoggingJobId() {
+ return mLoggingJobId;
+ }
+
public void printUniqueId(PrintWriter pw) {
if (mNamespace != null) {
pw.print(mNamespace);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9eb9d66..9bd2970 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3568,6 +3568,8 @@
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
field public CharSequence accessibilityTitle;
+ field public float preferredMaxDisplayRefreshRate;
+ field public float preferredMinDisplayRefreshRate;
field public int privateFlags;
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 81258f1..6e0fc4f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1084,6 +1084,16 @@
}
/**
+ * Update the forced status bar appearance.
+ * @hide
+ */
+ @Override
+ public void updateStatusBarAppearance(int appearance) {
+ mTaskDescription.setStatusBarAppearance(appearance);
+ setTaskDescription(mTaskDescription);
+ }
+
+ /**
* Update the forced navigation bar color.
* @hide
*/
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b5ee895..ff0f437 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -81,6 +81,7 @@
import android.util.DisplayMetrics;
import android.util.Singleton;
import android.util.Size;
+import android.view.WindowInsetsController.Appearance;
import android.window.TaskSnapshot;
import com.android.internal.app.LocalePicker;
@@ -1553,6 +1554,8 @@
private int mColorBackgroundFloating;
private int mStatusBarColor;
private int mNavigationBarColor;
+ @Appearance
+ private int mStatusBarAppearance;
private boolean mEnsureStatusBarContrastWhenTransparent;
private boolean mEnsureNavigationBarContrastWhenTransparent;
private int mResizeMode;
@@ -1653,8 +1656,8 @@
final Icon icon = mIconRes == Resources.ID_NULL ? null :
Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
- mStatusBarColor, mNavigationBarColor, false, false, RESIZE_MODE_RESIZEABLE,
- -1, -1, 0);
+ mStatusBarColor, mNavigationBarColor, 0, false, false,
+ RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
}
@@ -1672,7 +1675,7 @@
@Deprecated
public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1690,7 +1693,7 @@
@Deprecated
public TaskDescription(String label, @DrawableRes int iconRes) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1702,7 +1705,7 @@
*/
@Deprecated
public TaskDescription(String label) {
- this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1712,7 +1715,7 @@
*/
@Deprecated
public TaskDescription() {
- this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1728,7 +1731,7 @@
@Deprecated
public TaskDescription(String label, Bitmap icon, int colorPrimary) {
this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
- false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1744,14 +1747,15 @@
*/
@Deprecated
public TaskDescription(String label, Bitmap icon) {
- this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
- RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false,
+ false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/** @hide */
public TaskDescription(@Nullable String label, @Nullable Icon icon,
int colorPrimary, int colorBackground,
int statusBarColor, int navigationBarColor,
+ @Appearance int statusBarAppearance,
boolean ensureStatusBarContrastWhenTransparent,
boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
int minHeight, int colorBackgroundFloating) {
@@ -1761,6 +1765,7 @@
mColorBackground = colorBackground;
mStatusBarColor = statusBarColor;
mNavigationBarColor = navigationBarColor;
+ mStatusBarAppearance = statusBarAppearance;
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
ensureNavigationBarContrastWhenTransparent;
@@ -1789,6 +1794,7 @@
mColorBackground = other.mColorBackground;
mStatusBarColor = other.mStatusBarColor;
mNavigationBarColor = other.mNavigationBarColor;
+ mStatusBarAppearance = other.mStatusBarAppearance;
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1818,6 +1824,9 @@
if (other.mNavigationBarColor != 0) {
mNavigationBarColor = other.mNavigationBarColor;
}
+ if (other.mStatusBarAppearance != 0) {
+ mStatusBarAppearance = other.mStatusBarAppearance;
+ }
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
@@ -2089,6 +2098,14 @@
/**
* @hide
*/
+ @Appearance
+ public int getStatusBarAppearance() {
+ return mStatusBarAppearance;
+ }
+
+ /**
+ * @hide
+ */
public void setEnsureStatusBarContrastWhenTransparent(
boolean ensureStatusBarContrastWhenTransparent) {
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
@@ -2097,6 +2114,13 @@
/**
* @hide
*/
+ public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
+ mStatusBarAppearance = statusBarAppearance;
+ }
+
+ /**
+ * @hide
+ */
public boolean getEnsureNavigationBarContrastWhenTransparent() {
return mEnsureNavigationBarContrastWhenTransparent;
}
@@ -2218,6 +2242,7 @@
dest.writeInt(mColorBackground);
dest.writeInt(mStatusBarColor);
dest.writeInt(mNavigationBarColor);
+ dest.writeInt(mStatusBarAppearance);
dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
dest.writeInt(mResizeMode);
@@ -2241,6 +2266,7 @@
mColorBackground = source.readInt();
mStatusBarColor = source.readInt();
mNavigationBarColor = source.readInt();
+ mStatusBarAppearance = source.readInt();
mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
mResizeMode = source.readInt();
@@ -2289,6 +2315,7 @@
&& mColorBackground == other.mColorBackground
&& mStatusBarColor == other.mStatusBarColor
&& mNavigationBarColor == other.mNavigationBarColor
+ && mStatusBarAppearance == other.mStatusBarAppearance
&& mEnsureStatusBarContrastWhenTransparent
== other.mEnsureStatusBarContrastWhenTransparent
&& mEnsureNavigationBarContrastWhenTransparent
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 783e7d3..b2a9230 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -581,6 +581,7 @@
* <li>{@link #LOCK_TASK_FEATURE_HOME}</li>
* <li>{@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}</li>
* <li>{@link #LOCK_TASK_FEATURE_NOTIFICATIONS}</li>
+ * <li>{@link #LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK}</li>
* </ul>
* <li>{@link #getLockTaskFeatures(ComponentName)}</li>
* <li>{@link #setLockTaskPackages(ComponentName, String[])}</li>
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e23bbc6..d3bde4b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.ImageFormat;
import android.hardware.ICameraService;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
@@ -1478,6 +1479,12 @@
}
}
+ // Allow RAW formats, even when not advertised.
+ if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10
+ || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) {
+ return true;
+ }
+
if (validFormat == false) {
return false;
}
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 2e64a74..ff47f3f 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -444,7 +444,7 @@
* Retrieve the VpnProfileState for the profile provisioned by the calling package.
*
* @return the VpnProfileState with current information, or null if there was no profile
- * provisioned by the calling package.
+ * provisioned and started by the calling package.
*/
@Nullable
public VpnProfileState getProvisionedVpnProfileState() {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7d68b44..84f1880 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -736,7 +736,7 @@
* The default value is <code>false</code>.
*
* <p>Holders of the permission
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>Key for user restrictions.
@@ -941,7 +941,7 @@
* this restriction will be set as a base restriction which cannot be removed by any admin.
*
* <p>Holders of the permission
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>Key for user restrictions.
@@ -1451,7 +1451,7 @@
* affected. The default value is <code>false</code>.
*
* <p>Holders of the permission
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>Key for user restrictions.
@@ -1597,7 +1597,7 @@
* set.
*
* <p>Holders of the permission
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>The default value is <code>false</code>.
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 737c95f..f0140e1 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -88,6 +88,18 @@
"android.service.contentcapture.ContentCaptureService";
/**
+ * The {@link Intent} that must be declared as handled by the protection service.
+ *
+ * <p>To be supported, the service must also require the {@link
+ * android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE} permission so that other
+ * applications can not abuse it.
+ *
+ * @hide
+ */
+ public static final String PROTECTION_SERVICE_INTERFACE =
+ "android.service.contentcapture.ContentProtectionService";
+
+ /**
* Name under which a ContentCaptureService component publishes information about itself.
*
* <p>This meta-data should reference an XML resource containing a
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7596459..2f04b0c 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -658,6 +658,12 @@
void updateStatusBarColor(int color);
/**
+ * Update the status bar appearance.
+ */
+
+ void updateStatusBarAppearance(int appearance);
+
+ /**
* Update the navigation bar color to a forced one.
*/
void updateNavigationBarColor(int color);
@@ -1039,6 +1045,9 @@
if (mDecorCallback != null) {
mDecorCallback.onSystemBarAppearanceChanged(appearance);
}
+ if (mWindowControllerCallback != null) {
+ mWindowControllerCallback.updateStatusBarAppearance(appearance);
+ }
}
/** @hide */
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d40c032..adf772b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3763,6 +3763,7 @@
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @TestApi
public float preferredMinDisplayRefreshRate;
/**
@@ -3771,6 +3772,7 @@
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @TestApi
public float preferredMaxDisplayRefreshRate;
/** Indicates whether this window wants the HDR conversion is disabled. */
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index c37e311..c9afdc0 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
@@ -446,6 +447,9 @@
@Nullable // set on-demand by addDumpable()
private Dumper mDumpable;
+ // Created here in order to live across activity and session changes
+ @Nullable private final RingBuffer<ContentCaptureEvent> mContentProtectionEventBuffer;
+
/** @hide */
public interface ContentCaptureClient {
/**
@@ -456,12 +460,15 @@
}
/** @hide */
- static class StrippedContext {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static class StrippedContext {
@NonNull final String mPackageName;
@NonNull final String mContext;
final @UserIdInt int mUserId;
- private StrippedContext(@NonNull Context context) {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public StrippedContext(@NonNull Context context) {
mPackageName = context.getPackageName();
mContext = context.toString();
mUserId = context.getUserId();
@@ -502,6 +509,16 @@
mHandler = Handler.createAsync(Looper.getMainLooper());
mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
+
+ if (mOptions.contentProtectionOptions.enableReceiver
+ && mOptions.contentProtectionOptions.bufferSize > 0) {
+ mContentProtectionEventBuffer =
+ new RingBuffer(
+ ContentCaptureEvent.class,
+ mOptions.contentProtectionOptions.bufferSize);
+ } else {
+ mContentProtectionEventBuffer = null;
+ }
}
/**
@@ -870,6 +887,13 @@
activity.addDumpable(mDumpable);
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @Nullable
+ public RingBuffer<ContentCaptureEvent> getContentProtectionEventBuffer() {
+ return mContentProtectionEventBuffer;
+ }
+
// NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API
private final class Dumper implements Dumpable {
@Override
diff --git a/core/java/android/view/contentcapture/OWNERS b/core/java/android/view/contentcapture/OWNERS
index d1eda96..a958bbe 100644
--- a/core/java/android/view/contentcapture/OWNERS
+++ b/core/java/android/view/contentcapture/OWNERS
@@ -6,4 +6,5 @@
lpeter@google.com
tymtsai@google.com
hackz@google.com
-volnov@google.com
\ No newline at end of file
+shivanker@google.com
+volnov@google.com
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
new file mode 100644
index 0000000..0840b66
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.text.InputType;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
+ *
+ * @hide
+ */
+public class ContentProtectionEventProcessor {
+
+ private static final String TAG = "ContentProtectionEventProcessor";
+
+ private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ InputType.TYPE_NUMBER_VARIATION_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
+
+ private static final List<String> PASSWORD_TEXTS =
+ Collections.unmodifiableList(
+ Arrays.asList("password", "pass word", "code", "pin", "credential"));
+
+ private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
+ Collections.unmodifiableList(
+ Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
+
+ private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
+
+ private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
+
+ private static final Set<Integer> EVENT_TYPES_TO_STORE =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ ContentCaptureEvent.TYPE_VIEW_APPEARED,
+ ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+ ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+
+ @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer;
+
+ @NonNull private final Handler mHandler;
+
+ @NonNull private final IContentCaptureManager mContentCaptureManager;
+
+ @NonNull private final String mPackageName;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public boolean mPasswordFieldDetected = false;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public boolean mSuspiciousTextDetected = false;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public Instant mLastFlushTime;
+
+ public ContentProtectionEventProcessor(
+ @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
+ @NonNull Handler handler,
+ @NonNull IContentCaptureManager contentCaptureManager,
+ @NonNull String packageName) {
+ mEventBuffer = eventBuffer;
+ mHandler = handler;
+ mContentCaptureManager = contentCaptureManager;
+ mPackageName = packageName;
+ }
+
+ /** Main entry point for {@link ContentCaptureEvent} processing. */
+ @UiThread
+ public void processEvent(@NonNull ContentCaptureEvent event) {
+ if (EVENT_TYPES_TO_STORE.contains(event.getType())) {
+ storeEvent(event);
+ }
+ if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) {
+ processViewAppearedEvent(event);
+ }
+ }
+
+ @UiThread
+ private void storeEvent(@NonNull ContentCaptureEvent event) {
+ // Ensure receiver gets the package name which might not be set
+ ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode();
+ viewNode.setTextIdEntry(mPackageName);
+ event.setViewNode(viewNode);
+ mEventBuffer.append(event);
+ }
+
+ @UiThread
+ private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
+ mPasswordFieldDetected |= isPasswordField(event);
+ mSuspiciousTextDetected |= isSuspiciousText(event);
+ if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+ loginDetected();
+ }
+ }
+
+ @UiThread
+ private void loginDetected() {
+ if (mLastFlushTime == null
+ || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
+ flush();
+ }
+ mPasswordFieldDetected = false;
+ mSuspiciousTextDetected = false;
+ }
+
+ @UiThread
+ private void flush() {
+ mLastFlushTime = Instant.now();
+
+ // Note the thread annotations, do not move clearEvents to mHandler
+ ParceledListSlice<ContentCaptureEvent> events = clearEvents();
+ mHandler.post(() -> handlerOnLoginDetected(events));
+ }
+
+ @UiThread
+ @NonNull
+ private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+ List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray());
+ mEventBuffer.clear();
+ return new ParceledListSlice<>(events);
+ }
+
+ private void handlerOnLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+ try {
+ // TODO(b/275732576): Call mContentCaptureManager
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to flush events for: " + mPackageName, ex);
+ }
+ }
+
+ private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
+ return isPasswordField(event.getViewNode());
+ }
+
+ private boolean isPasswordField(@Nullable ViewNode viewNode) {
+ if (viewNode == null) {
+ return false;
+ }
+ return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
+ }
+
+ private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
+ if (!isAndroidViewNode(viewNode)) {
+ return false;
+ }
+ int inputType = viewNode.getInputType();
+ return PASSWORD_FIELD_INPUT_TYPES.stream()
+ .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
+ }
+
+ private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
+ if (viewNode.getClassName() != null) {
+ return false;
+ }
+ return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
+ }
+
+ private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
+ String className = viewNode.getClassName();
+ return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
+ }
+
+ private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
+ return isSuspiciousText(ContentProtectionUtils.getEventText(event))
+ || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
+ }
+
+ private boolean isSuspiciousText(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ if (isPasswordText(text)) {
+ return true;
+ }
+ String lowerCaseText = text.toLowerCase();
+ return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
+ .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
+ }
+
+ private boolean isPasswordText(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ String lowerCaseText = text.toLowerCase();
+ return PASSWORD_TEXTS.stream()
+ .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
+ }
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7f96266..3be8c3d 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -232,6 +232,7 @@
private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
+ private static final int SET_REMOTE_ADAPTER_TAG = 33;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -960,6 +961,11 @@
return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
}
+ @Override
+ public String getUniqueKey() {
+ return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+ }
+
int viewTypeCount;
ArrayList<RemoteViews> list;
}
@@ -1082,6 +1088,11 @@
public int getActionTag() {
return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
}
+
+ @Override
+ public String getUniqueKey() {
+ return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+ }
}
private class SetRemoteViewsAdapterIntent extends Action {
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8c05130..a92ca8a 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -424,8 +424,8 @@
case TRANSIT_NONE: return "NONE";
case TRANSIT_OPEN: return "OPEN";
case TRANSIT_CLOSE: return "CLOSE";
- case TRANSIT_TO_FRONT: return "SHOW";
- case TRANSIT_TO_BACK: return "HIDE";
+ case TRANSIT_TO_FRONT: return "TO_FRONT";
+ case TRANSIT_TO_BACK: return "TO_BACK";
case TRANSIT_CHANGE: return "CHANGE";
default: return "<unknown:" + mode + ">";
}
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 94c230b..95c3419 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -27,4 +27,10 @@
* the min value, there will be no obvious magnification effect.
*/
public static final float PERSISTED_SCALE_MIN_VALUE = 1.3f;
+
+ /** Minimum supported value for magnification scale. */
+ public static final float SCALE_MIN_VALUE = 1.0f;
+
+ /** Maximum supported value for magnification scale. */
+ public static final float SCALE_MAX_VALUE = 8.0f;
}
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index 3ba4ea5..80d753c 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -120,6 +120,10 @@
private long mPreDumpIfLockTooSlowStartUptime;
private long mPreDumpIfLockTooSlowDuration = 0;
+ private long mNotifyAppUnresponsiveStartUptime;
+ private long mNotifyAppUnresponsiveDuration = 0;
+ private long mNotifyWindowUnresponsiveStartUptime;
+ private long mNotifyWindowUnresponsiveDuration = 0;
private final int mAnrRecordPlacedOnQueueCookie =
sNextAnrRecordPlacedOnQueueCookieGenerator.incrementAndGet();
@@ -425,11 +429,36 @@
anrSkipped("dumpStackTraces");
}
+ /** Records the start of AnrController#notifyAppUnresponsive. */
+ public void notifyAppUnresponsiveStarted() {
+ mNotifyAppUnresponsiveStartUptime = getUptimeMillis();
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyAppUnresponsive()");
+ }
+
+ /** Records the end of AnrController#notifyAppUnresponsive. */
+ public void notifyAppUnresponsiveEnded() {
+ mNotifyAppUnresponsiveDuration = getUptimeMillis() - mNotifyAppUnresponsiveStartUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /** Records the start of AnrController#notifyWindowUnresponsive. */
+ public void notifyWindowUnresponsiveStarted() {
+ mNotifyWindowUnresponsiveStartUptime = getUptimeMillis();
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyWindowUnresponsive()");
+ }
+
+ /** Records the end of AnrController#notifyWindowUnresponsive. */
+ public void notifyWindowUnresponsiveEnded() {
+ mNotifyWindowUnresponsiveDuration = getUptimeMillis()
+ - mNotifyWindowUnresponsiveStartUptime;
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
/**
* Returns latency data as a comma separated value string for inclusion in ANR report.
*/
public String dumpAsCommaSeparatedArrayWithHeader() {
- return "DurationsV4: " + mAnrTriggerUptime
+ return "DurationsV5: " + mAnrTriggerUptime
/* triggering_to_app_not_responding_duration = */
+ "," + (mAppNotRespondingStartUptime - mAnrTriggerUptime)
/* app_not_responding_duration = */
@@ -480,6 +509,10 @@
+ "," + (mCopyingFirstPidSucceeded ? 1 : 0)
/* preDumpIfLockTooSlow_duration = */
+ "," + mPreDumpIfLockTooSlowDuration
+ /* notifyAppUnresponsive_duration = */
+ + "," + mNotifyAppUnresponsiveDuration
+ /* notifyWindowUnresponsive_duration = */
+ + "," + mNotifyWindowUnresponsiveDuration
+ "\n\n";
}
diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto
index 700baa1..7fe24670 100644
--- a/core/proto/android/companion/telecom.proto
+++ b/core/proto/android/companion/telecom.proto
@@ -45,10 +45,18 @@
AUDIO_PROCESSING = 5;
RINGING_SIMULATED = 6;
DISCONNECTED = 7;
+ DIALING = 8;
}
Status status = 3;
repeated Control controls = 4;
+
+ enum Direction {
+ UNKNOWN_DIRECTION = 0;
+ INCOMING = 1;
+ OUTGOING = 2;
+ }
+ Direction direction = 5;
}
message Request {
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 81eb213..5ac99db 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -128,6 +128,7 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
true, // ensureStatusBarContrastWhenTransparent
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
@@ -152,6 +153,7 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -167,6 +169,7 @@
0x2222222, // colorBackground
0x3333332, // statusBarColor
0x4444442, // navigationBarColor
+ 0, // statusBarAppearance
true, // ensureStatusBarContrastWhenTransparent
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
@@ -197,6 +200,7 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -219,6 +223,7 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -250,6 +255,7 @@
assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
assertEquals(td1.getResizeMode(), td2.getResizeMode());
assertEquals(td1.getMinWidth(), td2.getMinWidth());
assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 17ed4c4..101f7c2 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -15,14 +15,17 @@
*/
package android.view.contentcapture;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertThrows;
import android.content.ContentCaptureOptions;
import android.content.Context;
+import com.android.internal.util.RingBuffer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -37,9 +40,15 @@
@RunWith(MockitoJUnitRunner.class)
public class ContentCaptureManagerTest {
+ private static final int BUFFER_SIZE = 100;
+
+ private static final ContentCaptureOptions EMPTY_OPTIONS = new ContentCaptureOptions(null);
+
@Mock
private Context mMockContext;
+ @Mock private IContentCaptureManager mMockContentCaptureManager;
+
@Test
public void testConstructor_invalidParametersThrowsException() {
assertThrows(NullPointerException.class,
@@ -48,11 +57,65 @@
}
@Test
+ public void testConstructor_contentProtection_default_bufferNotCreated() {
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_disabled_bufferNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ false, BUFFER_SIZE));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_invalidBufferSize_bufferNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true, /* bufferSize= */ 0));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_enabled_bufferCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true, BUFFER_SIZE));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+ RingBuffer<ContentCaptureEvent> buffer = manager.getContentProtectionEventBuffer();
+
+ assertThat(buffer).isNotNull();
+ ContentCaptureEvent[] expected = new ContentCaptureEvent[BUFFER_SIZE];
+ int offset = 3;
+ for (int i = 0; i < BUFFER_SIZE + offset; i++) {
+ ContentCaptureEvent event = new ContentCaptureEvent(i, TYPE_SESSION_STARTED);
+ buffer.append(event);
+ expected[(i + BUFFER_SIZE - offset) % BUFFER_SIZE] = event;
+ }
+ assertThat(buffer.toArray()).isEqualTo(expected);
+ }
+
+ @Test
public void testRemoveData_invalidParametersThrowsException() {
- final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
- final ContentCaptureOptions options = new ContentCaptureOptions(null);
final ContentCaptureManager manager =
- new ContentCaptureManager(mMockContext, mockService, options);
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
assertThrows(NullPointerException.class, () -> manager.removeData(null));
}
@@ -60,10 +123,8 @@
@Test
@SuppressWarnings("GuardedBy")
public void testFlushViewTreeAppearingEventDisabled_setAndGet() {
- final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
- final ContentCaptureOptions options = new ContentCaptureOptions(null);
final ContentCaptureManager manager =
- new ContentCaptureManager(mMockContext, mockService, options);
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
manager.setFlushViewTreeAppearingEventDisabled(true);
@@ -71,4 +132,18 @@
manager.setFlushViewTreeAppearingEventDisabled(false);
assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
}
+
+ private ContentCaptureOptions createOptions(
+ ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+ return new ContentCaptureOptions(
+ /* loggingLevel= */ 0,
+ /* maxBufferSize= */ 0,
+ /* idleFlushingFrequencyMs= */ 0,
+ /* textChangeFlushingFrequencyMs= */ 0,
+ /* logHistorySize= */ 0,
+ /* disableFlushForViewTreeAppearing= */ false,
+ /* enableReceiver= */ true,
+ contentProtectionOptions,
+ /* whitelistedComponents= */ null);
+ }
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
new file mode 100644
index 0000000..fd5627d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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.annotation.Nullable;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.InputType;
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.RingBuffer;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test for {@link ContentProtectionEventProcessor}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionEventProcessorTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionEventProcessorTest {
+
+ private static final String PACKAGE_NAME = "com.test.package.name";
+
+ private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+
+ private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+
+ private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+
+ private static final String SAFE_TEXT = "SAFE TEXT";
+
+ private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
+
+ private static final Set<Integer> EVENT_TYPES_TO_STORE =
+ ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer;
+
+ @Mock private IContentCaptureManager mMockContentCaptureManager;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
+ @Before
+ public void setup() {
+ mContentProtectionEventProcessor =
+ new ContentProtectionEventProcessor(
+ mMockEventBuffer,
+ new Handler(Looper.getMainLooper()),
+ mMockContentCaptureManager,
+ PACKAGE_NAME);
+ }
+
+ @Test
+ public void processEvent_buffer_storesOnlySubsetOfEventTypes() {
+ List<ContentCaptureEvent> expectedEvents = new ArrayList<>();
+ for (int type = -100; type <= 100; type++) {
+ ContentCaptureEvent event = createEvent(type);
+ if (EVENT_TYPES_TO_STORE.contains(type)) {
+ expectedEvents.add(event);
+ }
+
+ mContentProtectionEventProcessor.processEvent(event);
+ }
+
+ assertThat(expectedEvents).hasSize(EVENT_TYPES_TO_STORE.size());
+ expectedEvents.forEach((expectedEvent) -> verify(mMockEventBuffer).append(expectedEvent));
+ verifyNoMoreInteractions(mMockEventBuffer);
+ }
+
+ @Test
+ public void processEvent_buffer_setsTextIdEntry_withoutExistingViewNode() {
+ ContentCaptureEvent event = createStoreEvent();
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(event.getViewNode()).isNotNull();
+ assertThat(event.getViewNode().getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+ verify(mMockEventBuffer).append(event);
+ }
+
+ @Test
+ public void processEvent_buffer_setsTextIdEntry_withExistingViewNode() {
+ ViewNode viewNode = new ViewNode();
+ viewNode.setTextIdEntry(PACKAGE_NAME + "TO BE OVERWRITTEN");
+ ContentCaptureEvent event = createStoreEvent();
+ event.setViewNode(viewNode);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(event.getViewNode()).isSameInstanceAs(viewNode);
+ assertThat(viewNode.getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+ verify(mMockEventBuffer).append(event);
+ }
+
+ @Test
+ public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+ for (int type = -100; type <= 100; type++) {
+ if (type == TYPE_VIEW_APPEARED) {
+ continue;
+ }
+
+ mContentProtectionEventProcessor.processEvent(createEvent(type));
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ }
+
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void processEvent_loginDetected() {
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ }
+
+ @Test
+ public void processEvent_loginDetected_passwordFieldNotDetected() {
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void processEvent_loginDetected_suspiciousTextNotDetected() {
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void processEvent_loginDetected_withoutViewNode() {
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void processEvent_multipleLoginsDetected_belowFlushThreshold() {
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ }
+
+ @Test
+ public void processEvent_multipleLoginsDetected_aboveFlushThreshold() {
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, times(2)).clear();
+ verify(mMockEventBuffer, times(2)).toArray();
+ }
+
+ @Test
+ public void isPasswordField_android() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_android_withoutClassName() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_android_wrongClassName() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ "wrong.prefix" + ANDROID_CLASS_NAME,
+ InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_android_wrongInputType() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_webView() {
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[0]);
+
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ }
+
+ @Test
+ public void isPasswordField_webView_withClassName() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_webView_withSafeViewNodeText() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ null, /* eventText= */ null, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isPasswordField_webView_withEventText() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_withSafeText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_eventText_suspiciousText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_viewNodeText_suspiciousText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_eventText_passwordText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
+ public void isSuspiciousText_viewNodeText_passwordText() {
+ // Specify the class to differ from {@link isPasswordField_webView} test in this version
+ ContentCaptureEvent event =
+ createProcessEvent(
+ "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ private static ContentCaptureEvent createEvent(int type) {
+ return new ContentCaptureEvent(/* sessionId= */ 123, type);
+ }
+
+ private static ContentCaptureEvent createStoreEvent() {
+ return createEvent(TYPE_VIEW_TEXT_CHANGED);
+ }
+
+ private static ContentCaptureEvent createProcessEvent() {
+ return createEvent(TYPE_VIEW_APPEARED);
+ }
+
+ private ContentCaptureEvent createProcessEvent(
+ @Nullable String className,
+ int inputType,
+ @Nullable String eventText,
+ @Nullable String viewNodeText) {
+ View view = new View(mContext);
+ ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+ if (className != null) {
+ viewStructure.setClassName(className);
+ }
+ if (viewNodeText != null) {
+ viewStructure.setText(viewNodeText);
+ }
+ viewStructure.setInputType(inputType);
+
+ ContentCaptureEvent event = createProcessEvent();
+ event.setViewNode(viewStructure.getNode());
+ if (eventText != null) {
+ event.setText(eventText);
+ }
+
+ return event;
+ }
+
+ private ContentCaptureEvent createAndroidPasswordFieldEvent(
+ @Nullable String className, int inputType) {
+ return createProcessEvent(
+ className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+ }
+
+ private ContentCaptureEvent createWebViewPasswordFieldEvent(
+ @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
+ return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+ }
+
+ private ContentCaptureEvent createSuspiciousTextEvent(
+ @Nullable String eventText, @Nullable String viewNodeText) {
+ return createProcessEvent(
+ /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
new file mode 100644
index 0000000..81592c3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * Controller to manage behavior of activities launched with
+ * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT].
+ */
+class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) {
+
+ /** Allows to temporarily disable launch adjacent handling */
+ var launchAdjacentEnabled: Boolean = true
+ set(value) {
+ if (field != value) {
+ KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value)
+ field = value
+ container?.let { c ->
+ if (value) {
+ enableContainer(c)
+ } else {
+ disableContainer((c))
+ }
+ }
+ }
+ }
+ private var container: WindowContainerToken? = null
+
+ /**
+ * Set [container] as the new launch adjacent flag root container.
+ *
+ * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the
+ * container until after it is enabled again.
+ *
+ * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot
+ */
+ fun setLaunchAdjacentRoot(container: WindowContainerToken) {
+ KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container")
+ this.container = container
+ if (launchAdjacentEnabled) {
+ enableContainer(container)
+ }
+ }
+
+ /**
+ * Clear a container previously set through [setLaunchAdjacentRoot].
+ *
+ * Always clears the container, regardless of [launchAdjacentEnabled] value.
+ *
+ * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot
+ */
+ fun clearLaunchAdjacentRoot() {
+ KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container")
+ container?.let {
+ disableContainer(it)
+ container = null
+ }
+ }
+
+ private fun enableContainer(container: WindowContainerToken) {
+ KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container")
+ val wct = WindowContainerTransaction()
+ wct.setLaunchAdjacentFlagRoot(container)
+ syncQueue.queue(wct)
+ }
+
+ private fun disableContainer(container: WindowContainerToken) {
+ KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container")
+ val wct = WindowContainerTransaction()
+ wct.clearLaunchAdjacentFlagRoot(container)
+ syncQueue.queue(wct)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index d191fcd..37cbbcd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -482,7 +482,7 @@
val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
ta.recycle()
info.changes
- .filter { it.taskInfo.windowingMode == WINDOWING_MODE_FREEFORM }
+ .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
.forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 167c032..779c539 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -45,6 +45,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -513,13 +514,19 @@
final boolean isCloseAction = mCloseAction != null && Objects.equals(
mCloseAction.getActionIntent(), action.getActionIntent());
- // TODO: Check if the action drawable has changed before we reload it
- action.getIcon().loadDrawableAsync(mContext, d -> {
- if (d != null) {
- d.setTint(Color.WHITE);
- actionView.setImageDrawable(d);
- }
- }, mMainHandler);
+ final int iconType = action.getIcon().getType();
+ if (iconType == Icon.TYPE_URI || iconType == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+ // Disallow loading icon from content URI
+ actionView.setImageDrawable(null);
+ } else {
+ // TODO: Check if the action drawable has changed before we reload it
+ action.getIcon().loadDrawableAsync(mContext, d -> {
+ if (d != null) {
+ d.setTint(Color.WHITE);
+ actionView.setImageDrawable(d);
+ }
+ }, mMainHandler);
+ }
actionView.setCustomCloseBackgroundVisibility(
isCloseAction ? View.VISIBLE : View.GONE);
actionView.setContentDescription(action.getContentDescription());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index c286959..8723f9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -35,6 +35,8 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.IntArray;
+import android.util.Pair;
import android.util.Slog;
import android.view.Display;
import android.view.IRecentsAnimationController;
@@ -135,8 +137,12 @@
@Override
public WindowContainerTransaction handleRequest(IBinder transition,
TransitionRequestInfo request) {
- // do not directly handle requests. Only entry point should be via startRecentsTransition
- // TODO: Only log an error if the transition is a recents transition
+ if (mControllers.isEmpty()) {
+ // Ignore if there is no running recents transition
+ return null;
+ }
+ final RecentsController controller = mControllers.get(mControllers.size() - 1);
+ controller.handleMidTransitionRequest(request);
return null;
}
@@ -239,6 +245,11 @@
/** The latest state that the recents animation is operating in. */
private int mState = STATE_NORMAL;
+ // Snapshots taken when a new display change transition is requested, prior to the display
+ // change being applied. This pending set of snapshots will only be applied when cancel is
+ // next called.
+ private Pair<int[], TaskSnapshot[]> mPendingPauseSnapshotsForCancel;
+
RecentsController(IRecentsAnimationRunner listener) {
mInstanceId = System.identityHashCode(this);
mListener = listener;
@@ -290,6 +301,16 @@
* "replace-with-screenshot" like behavior.
*/
private boolean sendCancelWithSnapshots() {
+ Pair<int[], TaskSnapshot[]> snapshots = mPendingPauseSnapshotsForCancel != null
+ ? mPendingPauseSnapshotsForCancel
+ : getSnapshotsForPausingTasks();
+ return sendCancel(snapshots.first, snapshots.second);
+ }
+
+ /**
+ * Snapshots the pausing tasks and returns the mapping of the taskId -> snapshot.
+ */
+ private Pair<int[], TaskSnapshot[]> getSnapshotsForPausingTasks() {
int[] taskIds = null;
TaskSnapshot[] snapshots = null;
if (mPausingTasks.size() > 0) {
@@ -298,6 +319,9 @@
try {
for (int i = 0; i < mPausingTasks.size(); ++i) {
TaskState state = mPausingTasks.get(0);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.sendCancel: Snapshotting task=%d",
+ mInstanceId, state.mTaskInfo.taskId);
snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot(
state.mTaskInfo.taskId, true /* updateCache */);
}
@@ -306,7 +330,7 @@
snapshots = null;
}
}
- return sendCancel(taskIds, snapshots);
+ return new Pair(taskIds, snapshots);
}
/**
@@ -315,7 +339,7 @@
private boolean sendCancel(@Nullable int[] taskIds,
@Nullable TaskSnapshot[] taskSnapshots) {
try {
- final String cancelDetails = taskSnapshots != null ? " with snapshots" : "";
+ final String cancelDetails = taskSnapshots != null ? "with snapshots" : "";
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.cancel: calling onAnimationCanceled %s",
mInstanceId, cancelDetails);
@@ -348,6 +372,7 @@
mOpeningTasks = null;
mInfo = null;
mTransition = null;
+ mPendingPauseSnapshotsForCancel = null;
mControllers.remove(this);
}
@@ -398,26 +423,28 @@
// About layering: we divide up the "layer space" into 3 regions (each the size of
// the change count). This lets us categorize things into above/below/between
// while maintaining their relative ordering.
+ final int belowLayers = info.getChanges().size();
+ final int aboveLayers = info.getChanges().size() * 3;
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (TransitionUtil.isWallpaper(change)) {
final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
// wallpapers go into the "below" layer space
- info.getChanges().size() - i, info, t, mLeashMap);
+ belowLayers - i, info, t, mLeashMap);
wallpapers.add(target);
// Make all the wallpapers opaque since we want them visible from the start
t.setAlpha(target.leash, 1);
} else if (leafTaskFilter.test(change)) {
// start by putting everything into the "below" layer space.
final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
- info.getChanges().size() - i, info, t, mLeashMap);
+ belowLayers - i, info, t, mLeashMap);
apps.add(target);
if (TransitionUtil.isClosingType(change.getMode())) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " adding pausing taskId=%d", taskInfo.taskId);
+ " adding pausing leaf taskId=%d", taskInfo.taskId);
// raise closing (pausing) task to "above" layer so it isn't covered
- t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+ t.setLayer(target.leash, aboveLayers - i);
mPausingTasks.add(new TaskState(change, target.leash));
if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
// This can only happen if we have a separate recents/home (3p launcher)
@@ -430,17 +457,32 @@
} else if (taskInfo != null
&& taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
// There's a 3p launcher, so make sure recents goes above that.
- t.setLayer(target.leash, info.getChanges().size() * 3 - i);
+ t.setLayer(target.leash, aboveLayers - i);
} else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
// do nothing
} else if (TransitionUtil.isOpeningType(change.getMode())) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " adding opening taskId=%d", taskInfo.taskId);
+ " adding opening leaf taskId=%d", taskInfo.taskId);
mOpeningTasks.add(new TaskState(change, target.leash));
}
+ } else if (taskInfo != null && TransitionInfo.isIndependent(change, info)) {
+ // Root tasks
+ if (TransitionUtil.isClosingType(change.getMode())) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " adding pausing taskId=%d", taskInfo.taskId);
+ // raise closing (pausing) task to "above" layer so it isn't covered
+ t.setLayer(change.getLeash(), aboveLayers - i);
+ mPausingTasks.add(new TaskState(change, null /* leash */));
+ } else if (TransitionUtil.isOpeningType(change.getMode())) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " adding opening taskId=%d", taskInfo.taskId);
+ // Put into the "below" layer space.
+ t.setLayer(change.getLeash(), belowLayers - i);
+ mOpeningTasks.add(new TaskState(change, null /* leash */));
+ }
} else if (TransitionUtil.isDividerBar(change)) {
final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
- info.getChanges().size() - i, info, t, mLeashMap);
+ belowLayers - i, info, t, mLeashMap);
// Add this as a app and we will separate them on launcher side by window type.
apps.add(target);
}
@@ -460,6 +502,22 @@
return true;
}
+ /**
+ * Updates this controller when a new transition is requested mid-recents transition.
+ */
+ void handleMidTransitionRequest(TransitionRequestInfo request) {
+ if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null) {
+ final TransitionRequestInfo.DisplayChange dispChange = request.getDisplayChange();
+ if (dispChange.getStartRotation() != dispChange.getEndRotation()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "[%d] RecentsController.prepareForMerge: "
+ + "Snapshot due to requested display change",
+ mInstanceId);
+ mPendingPauseSnapshotsForCancel = getSnapshotsForPausingTasks();
+ }
+ }
+ }
+
@SuppressLint("NewApi")
void merge(TransitionInfo info, SurfaceControl.Transaction t,
Transitions.TransitionFinishCallback finishCallback) {
@@ -479,7 +537,9 @@
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge", mInstanceId);
+ // Keep all tasks in one list because order matters.
ArrayList<TransitionInfo.Change> openingTasks = null;
+ IntArray openingTaskIsLeafs = null;
ArrayList<TransitionInfo.Change> closingTasks = null;
mOpeningSeparateHome = false;
TransitionInfo.Change recentsOpening = null;
@@ -498,25 +558,29 @@
cancel("task #" + taskInfo.taskId + " is always_on_top");
return;
}
- hasTaskChange = hasTaskChange || taskInfo != null;
+ final boolean isRootTask = taskInfo != null
+ && TransitionInfo.isIndependent(change, info);
+ hasTaskChange = hasTaskChange || isRootTask;
final boolean isLeafTask = leafTaskFilter.test(change);
if (TransitionUtil.isOpeningType(change.getMode())) {
if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
recentsOpening = change;
- } else if (isLeafTask) {
- if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ } else if (isRootTask || isLeafTask) {
+ if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
// This is usually a 3p launcher
mOpeningSeparateHome = true;
}
if (openingTasks == null) {
openingTasks = new ArrayList<>();
+ openingTaskIsLeafs = new IntArray();
}
openingTasks.add(change);
+ openingTaskIsLeafs.add(isLeafTask ? 1 : 0);
}
} else if (TransitionUtil.isClosingType(change.getMode())) {
if (mRecentsTask != null && mRecentsTask.equals(change.getContainer())) {
foundRecentsClosing = true;
- } else if (isLeafTask) {
+ } else if (isRootTask || isLeafTask) {
if (closingTasks == null) {
closingTasks = new ArrayList<>();
}
@@ -526,6 +590,8 @@
// Finish recents animation if the display is changed, so the default
// transition handler can play the animation such as rotation effect.
if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) {
+ // This call to cancel will use the screenshots taken preemptively in
+ // handleMidTransitionRequest() prior to the display changing
cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
return;
}
@@ -580,7 +646,8 @@
}
final TaskState openingTask = mOpeningTasks.remove(openingIdx);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " pausing opening taskId=%d", openingTask.mTaskInfo.taskId);
+ " pausing opening %staskId=%d", openingTask.isLeaf() ? "leaf " : "",
+ openingTask.mTaskInfo.taskId);
mPausingTasks.add(openingTask);
didMergeThings = true;
}
@@ -590,35 +657,55 @@
// Switching to some new tasks, add to mOpening and remove from mPausing. Also,
// enter NEW_TASK state since this will start the switch-to animation.
final int layer = mInfo.getChanges().size() * 3;
- appearedTargets = new RemoteAnimationTarget[openingTasks.size()];
+ int openingLeafCount = 0;
+ for (int i = 0; i < openingTaskIsLeafs.size(); ++i) {
+ openingLeafCount += openingTaskIsLeafs.get(i);
+ }
+ if (openingLeafCount > 0) {
+ appearedTargets = new RemoteAnimationTarget[openingLeafCount];
+ }
+ int nextTargetIdx = 0;
for (int i = 0; i < openingTasks.size(); ++i) {
final TransitionInfo.Change change = openingTasks.get(i);
+ final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
// Something is showing/opening a previously-pausing app.
- appearedTargets[i] = TransitionUtil.newTarget(
- change, layer, mPausingTasks.get(pausingIdx).mLeash);
+ if (isLeaf) {
+ appearedTargets[nextTargetIdx++] = TransitionUtil.newTarget(
+ change, layer, mPausingTasks.get(pausingIdx).mLeash);
+ }
final TaskState pausingTask = mPausingTasks.remove(pausingIdx);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " opening pausing taskId=%d", pausingTask.mTaskInfo.taskId);
+ " opening pausing %staskId=%d", isLeaf ? "leaf " : "",
+ pausingTask.mTaskInfo.taskId);
mOpeningTasks.add(pausingTask);
// Setup hides opening tasks initially, so make it visible again (since we
// are already showing it).
t.show(change.getLeash());
t.setAlpha(change.getLeash(), 1.f);
- } else {
- // We are receiving new opening tasks, so convert to onTasksAppeared.
- appearedTargets[i] = TransitionUtil.newTarget(
+ } else if (isLeaf) {
+ // We are receiving new opening leaf tasks, so convert to onTasksAppeared.
+ final RemoteAnimationTarget target = TransitionUtil.newTarget(
change, layer, info, t, mLeashMap);
+ appearedTargets[nextTargetIdx++] = target;
// reparent into the original `mInfo` since that's where we are animating.
final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
- t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash());
- t.setLayer(appearedTargets[i].leash, layer);
+ t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
+ t.setLayer(target.leash, layer);
// Hide the animation leash, let listener show it.
- t.hide(appearedTargets[i].leash);
+ t.hide(target.leash);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " opening new taskId=%d", appearedTargets[i].taskId);
- mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash));
+ " opening new leaf taskId=%d", target.taskId);
+ mOpeningTasks.add(new TaskState(change, target.leash));
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " opening new taskId=%d", change.getTaskInfo().taskId);
+ t.setLayer(change.getLeash(), layer);
+ // Setup hides opening tasks initially, so make it visible since recents
+ // is only animating the leafs.
+ t.show(change.getLeash());
+ mOpeningTasks.add(new TaskState(change, null));
}
}
didMergeThings = true;
@@ -803,7 +890,7 @@
t.show(mOpeningTasks.get(i).mTaskSurface);
}
for (int i = 0; i < mPausingTasks.size(); ++i) {
- if (!sendUserLeaveHint) {
+ if (!sendUserLeaveHint && mPausingTasks.get(i).isLeaf()) {
// This means recents is not *actually* finishing, so of course we gotta
// do special stuff in WMCore to accommodate.
wct.setDoNotPip(mPausingTasks.get(i).mToken);
@@ -882,7 +969,8 @@
/** The surface/leash of the task provided by Core. */
SurfaceControl mTaskSurface;
- /** The (local) animation-leash created for this task. */
+ /** The (local) animation-leash created for this task. Only non-null for leafs. */
+ @Nullable
SurfaceControl mLeash;
TaskState(TransitionInfo.Change change, SurfaceControl leash) {
@@ -901,6 +989,10 @@
return -1;
}
+ boolean isLeaf() {
+ return mLeash != null;
+ }
+
public String toString() {
return "" + mToken + " : " + mLeash;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index b2526ee..366f4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -24,6 +24,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -2406,6 +2407,7 @@
if (change.getMode() == TRANSIT_CHANGE
&& (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
mSplitLayout.update(startTransaction);
+ record.mContainDisplayChange = true;
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
@@ -2435,7 +2437,13 @@
continue;
}
final StageTaskListener stage = getStageOfTask(taskInfo);
- if (stage == null) continue;
+ if (stage == null) {
+ if (change.getParent() == null && !isClosingType(change.getMode())
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ record.mContainShowPipChange = true;
+ }
+ continue;
+ }
if (isOpeningType(change.getMode())) {
if (!stage.containsTask(taskInfo.taskId)) {
Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
@@ -2450,13 +2458,22 @@
}
}
}
- // If the size of dismissStages == 1, one of the task is closed without prepare pending
- // transition, which could happen if all activities were finished after finish top
- // activity in a task, so the trigger task is null when handleRequest.
- // Note if the size of dismissStages == 2, it's starting a new task, so don't handle it.
final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
+ if (!record.mContainDisplayChange && record.mContainShowPipChange) {
+ // This occurred when split enter pip by open another fullscreen app so let
+ // pip tranistion handler to handle this but also start our dismiss transition.
+ // TODO(b/282894249): Should improve this case animation on pip.
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ mSplitTransitions.startDismissTransition(wct, this, STAGE_TYPE_UNDEFINED,
+ EXIT_REASON_CHILD_TASK_ENTER_PIP);
+ } else if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
|| dismissStages.size() == 1) {
+ // If the size of dismissStages == 1, one of the task is closed without prepare
+ // pending transition, which could happen if all activities were finished after
+ // finish top activity in a task, so the trigger task is null when handleRequest.
+ // Note if the size of dismissStages == 2, it's starting a new task,
+ // so don't handle it.
Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
+ "transition.");
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -2473,7 +2490,6 @@
// TODO(b/184679596): Find a way to either include task-org information in
// the transition, or synchronize task-org callbacks.
}
-
// Use normal animations.
return false;
} else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
@@ -2490,6 +2506,8 @@
}
static class StageChangeRecord {
+ boolean mContainDisplayChange = false;
+ boolean mContainShowPipChange = false;
static class StageChange {
final StageTaskListener mStageTaskListener;
final IntArray mAddedTaskId = new IntArray();
@@ -2622,6 +2640,7 @@
Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
mSplitTransitions.mPendingEnter.cancel((cancelWct, cancelT)
-> prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, cancelWct));
+ mSplitUnsupportedToast.show();
return true;
}
} else {
@@ -2632,6 +2651,7 @@
(sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
mSplitTransitions.mPendingEnter.cancel(
(cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ mSplitUnsupportedToast.show();
return true;
}
}
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 ce8d792..de20c2d 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
@@ -29,6 +29,7 @@
import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -554,7 +555,10 @@
layer = -zSplitLine - i;
}
} else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
- if (isOpening) {
+ if (isOpening
+ // This is for when an activity launches while a different transition is
+ // collecting.
+ || change.hasFlags(FLAG_MOVED_TO_TOP)) {
// put on top
layer = zSplitLine + numChanges - i;
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index bb0eba6..c504f57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -173,6 +173,17 @@
mTransition = null;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull TransitionFinishCallback finishCallback) {
+ if (info.getType() == TRANSIT_CHANGE) {
+ // Apply changes happening during the unfold animation immediately
+ t.apply();
+ finishCallback.onTransitionFinished(null, null);
+ }
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 143b42a..c33a633 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -121,15 +121,14 @@
@Override
public boolean test(TransitionInfo.Change change) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null) return false;
// Children always come before parent since changes are in top-to-bottom z-order.
- if ((taskInfo == null) || mChildTaskTargets.get(taskInfo.taskId)) {
- // has children, so not a leaf. Skip.
- return false;
- }
+ boolean hasChildren = mChildTaskTargets.get(taskInfo.taskId);
if (taskInfo.hasParentTask()) {
mChildTaskTargets.put(taskInfo.parentTaskId, true);
}
- return true;
+ // If it has children, it's not a leaf.
+ return !hasChildren;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
similarity index 89%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
index 09d474d..a97c19f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.desktopmode;
+package com.android.wm.shell;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -25,16 +25,16 @@
/**
* {@link WindowContainerToken} wrapper that supports a mock binder
*/
-class MockToken {
+public class MockToken {
private final WindowContainerToken mToken;
- MockToken() {
+ public MockToken() {
mToken = mock(WindowContainerToken.class);
IBinder binder = mock(IBinder.class);
when(mToken.asBinder()).thenReturn(binder);
}
- WindowContainerToken token() {
+ public WindowContainerToken token() {
return mToken;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
new file mode 100644
index 0000000..9dc816b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common
+
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerTransaction
+import android.window.WindowContainerTransaction.HierarchyOp
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LaunchAdjacentControllerTest : ShellTestCase() {
+
+ private lateinit var controller: LaunchAdjacentController
+
+ @Mock private lateinit var syncQueue: SyncTransactionQueue
+
+ @Before
+ fun setUp() {
+ controller = LaunchAdjacentController(syncQueue)
+ }
+
+ @Test
+ fun newInstance_enabledByDefault() {
+ assertThat(controller.launchAdjacentEnabled).isTrue()
+ }
+
+ @Test
+ fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() {
+ val token = MockToken().token()
+ controller.launchAdjacentEnabled = false
+ controller.setLaunchAdjacentRoot(token)
+ verify(syncQueue, never()).queue(any())
+ }
+
+ @Test
+ fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.clearLaunchAdjacentRoot()
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.launchAdjacentEnabled = false
+ clearInvocations(syncQueue)
+
+ controller.clearLaunchAdjacentRoot()
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.launchAdjacentEnabled = false
+ clearInvocations(syncQueue)
+
+ controller.launchAdjacentEnabled = true
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() {
+ controller.launchAdjacentEnabled = false
+ controller.launchAdjacentEnabled = true
+ verify(syncQueue, never()).queue(any())
+ }
+
+ @Test
+ fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.launchAdjacentEnabled = true
+ controller.launchAdjacentEnabled = true
+ // Only execute once
+ verify(syncQueue).queue(any())
+ }
+
+ @Test
+ fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ controller.launchAdjacentEnabled = false
+ val wct = getLatestTransactionOrFail()
+ assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder())
+ }
+
+ @Test
+ fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() {
+ controller.launchAdjacentEnabled = false
+ verify(syncQueue, never()).queue(any())
+ }
+
+ @Test
+ fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() {
+ val token = MockToken().token()
+ controller.setLaunchAdjacentRoot(token)
+ clearInvocations(syncQueue)
+ controller.launchAdjacentEnabled = false
+ controller.launchAdjacentEnabled = false
+ // Only execute once
+ verify(syncQueue).queue(any())
+ }
+
+ private fun getLatestTransactionOrFail(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(syncQueue, atLeastOnce()).queue(arg.capture())
+ return arg.allValues.last().also { assertThat(it).isNotNull() }
+ }
+}
+
+private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder {
+ return hierarchyOps
+ // Find the operation with the correct type
+ .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+ // For set flag root operation, toTop is false
+ .filter { op -> !op.toTop }
+ .map { it.container }
+ .first()
+}
+
+private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder {
+ return hierarchyOps
+ // Find the operation with the correct type
+ .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT }
+ // For clear flag root operation, toTop is true
+ .filter { op -> op.toTop }
+ .map { it.container }
+ .first()
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index d6387ee..605a762 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -59,6 +59,7 @@
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.MockToken;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index e3dd449..666ef55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -39,6 +39,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index cf1ff32..29a757c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -22,6 +22,7 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.view.Display.DEFAULT_DISPLAY
+import com.android.wm.shell.MockToken
import com.android.wm.shell.TestRunningTaskInfoBuilder
class DesktopTestHelpers {
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 3527eee..2a6dc7b 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -108,7 +108,7 @@
{
// Migrate existing contents into new ashmem region
- uint32_t slotsSize = mSize - mSlotsOffset;
+ uint32_t slotsSize = sizeOfSlots();
uint32_t newSlotsOffset = mInflatedSize - slotsSize;
memcpy(static_cast<uint8_t*>(newData),
static_cast<uint8_t*>(mData), mAllocOffset);
@@ -216,11 +216,9 @@
if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail;
} else {
// Since we know we're going to be read-only on the remote side,
- // we can compact ourselves on the wire, with just enough padding
- // to ensure our slots stay aligned
- size_t slotsSize = mSize - mSlotsOffset;
- size_t compactedSize = mAllocOffset + slotsSize;
- compactedSize = (compactedSize + 3) & ~3;
+ // we can compact ourselves on the wire.
+ size_t slotsSize = sizeOfSlots();
+ size_t compactedSize = sizeInUse();
if (parcel->writeUint32(compactedSize)) goto fail;
if (parcel->writeBool(false)) goto fail;
void* dest = parcel->writeInplace(compactedSize);
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index 6e55a9a..9ec026a 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -90,6 +90,9 @@
inline uint32_t getNumRows() { return mNumRows; }
inline uint32_t getNumColumns() { return mNumColumns; }
+ inline size_t sizeOfSlots() const { return mSize - mSlotsOffset; }
+ inline size_t sizeInUse() const { return mAllocOffset + sizeOfSlots(); }
+
status_t clear();
status_t setNumColumns(uint32_t numColumns);
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index d1cfd03..3cdb31e 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -21,9 +21,16 @@
#include "TestHelpers.h"
+// Verify that the memory in use is a multiple of 4 bytes
+#define ASSERT_ALIGNED(w) \
+ ASSERT_EQ(((w)->sizeInUse() & 3), 0); \
+ ASSERT_EQ(((w)->freeSpace() & 3), 0); \
+ ASSERT_EQ(((w)->sizeOfSlots() & 3), 0)
+
#define CREATE_WINDOW_1K \
CursorWindow* w; \
- CursorWindow::create(String8("test"), 1 << 10, &w);
+ CursorWindow::create(String8("test"), 1 << 10, &w); \
+ ASSERT_ALIGNED(w);
#define CREATE_WINDOW_1K_3X3 \
CursorWindow* w; \
@@ -31,11 +38,13 @@
ASSERT_EQ(w->setNumColumns(3), OK); \
ASSERT_EQ(w->allocRow(), OK); \
ASSERT_EQ(w->allocRow(), OK); \
- ASSERT_EQ(w->allocRow(), OK);
+ ASSERT_EQ(w->allocRow(), OK); \
+ ASSERT_ALIGNED(w);
#define CREATE_WINDOW_2M \
CursorWindow* w; \
- CursorWindow::create(String8("test"), 1 << 21, &w);
+ CursorWindow::create(String8("test"), 1 << 21, &w); \
+ ASSERT_ALIGNED(w);
static constexpr const size_t kHalfInlineSize = 8192;
static constexpr const size_t kGiantSize = 1048576;
@@ -49,6 +58,7 @@
ASSERT_EQ(w->getNumColumns(), 0);
ASSERT_EQ(w->size(), 1 << 10);
ASSERT_EQ(w->freeSpace(), 1 << 10);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, SetNumColumns) {
@@ -60,6 +70,7 @@
ASSERT_NE(w->setNumColumns(5), OK);
ASSERT_NE(w->setNumColumns(3), OK);
ASSERT_EQ(w->getNumColumns(), 4);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, SetNumColumnsAfterRow) {
@@ -70,6 +81,7 @@
ASSERT_EQ(w->allocRow(), OK);
ASSERT_NE(w->setNumColumns(4), OK);
ASSERT_EQ(w->getNumColumns(), 0);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, AllocRow) {
@@ -83,14 +95,17 @@
ASSERT_EQ(w->allocRow(), OK);
ASSERT_LT(w->freeSpace(), before);
ASSERT_EQ(w->getNumRows(), 1);
+ ASSERT_ALIGNED(w);
// Verify we can unwind
ASSERT_EQ(w->freeLastRow(), OK);
ASSERT_EQ(w->freeSpace(), before);
ASSERT_EQ(w->getNumRows(), 0);
+ ASSERT_ALIGNED(w);
// Can't unwind when no rows left
ASSERT_NE(w->freeLastRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, AllocRowBounds) {
@@ -100,6 +115,7 @@
ASSERT_EQ(w->setNumColumns(60), OK);
ASSERT_EQ(w->allocRow(), OK);
ASSERT_NE(w->allocRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreNull) {
@@ -116,6 +132,7 @@
auto field = w->getFieldSlot(0, 0);
ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreLong) {
@@ -134,6 +151,7 @@
ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreString) {
@@ -155,6 +173,7 @@
auto actual = w->getFieldSlotValueString(field, &size);
ASSERT_EQ(std::string(actual), "cafe");
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, StoreBounds) {
@@ -175,6 +194,7 @@
ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr);
ASSERT_EQ(w->getFieldSlot(0, -1), nullptr);
ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, Inflate) {
@@ -234,6 +254,7 @@
ASSERT_NE(actual, buf);
ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelEmpty) {
@@ -249,10 +270,12 @@
ASSERT_EQ(w->getNumColumns(), 0);
ASSERT_EQ(w->size(), 0);
ASSERT_EQ(w->freeSpace(), 0);
+ ASSERT_ALIGNED(w);
// We can't mutate the window after parceling
ASSERT_NE(w->setNumColumns(4), OK);
ASSERT_NE(w->allocRow(), OK);
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelSmall) {
@@ -311,6 +334,7 @@
ASSERT_EQ(actualSize, 0);
ASSERT_NE(actual, nullptr);
}
+ ASSERT_ALIGNED(w);
}
TEST(CursorWindowTest, ParcelLarge) {
@@ -364,6 +388,7 @@
ASSERT_EQ(actualSize, 0);
ASSERT_NE(actual, nullptr);
}
+ ASSERT_ALIGNED(w);
}
} // android
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 7b17cbd..19d74b3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,6 +16,8 @@
package com.android.packageinstaller;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+
import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
import android.app.Activity;
@@ -45,6 +47,9 @@
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
setResult(resultCode, data);
finish();
+ if (data != null && data.hasCategory(CATEGORY_LAUNCHER)) {
+ startActivity(data);
+ }
}
@Override
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 73c03a5..ff991d2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -17,7 +17,6 @@
package com.android.packageinstaller;
import android.app.Activity;
-import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -123,11 +122,7 @@
Button launchButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
if (enabled) {
launchButton.setOnClickListener(view -> {
- try {
- startActivity(mLaunchIntent);
- } catch (ActivityNotFoundException | SecurityException e) {
- Log.e(LOG_TAG, "Could not start activity", e);
- }
+ setResult(Activity.RESULT_OK, mLaunchIntent);
finish();
});
} else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d154156..e071c11 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -16,8 +16,9 @@
*/
package com.android.packageinstaller;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.Manifest;
@@ -789,7 +790,8 @@
}
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (!isDestroyed()) {
- startActivity(getIntent().addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT));
+ startActivity(getIntent().addFlags(
+ FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP));
}
}, 500);
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 750c156..c244ca0 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -69,9 +69,6 @@
"src/**/*.java",
"src/**/*.kt",
],
-
- min_sdk_version: "30",
-
}
// NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index dac7f8d..9f884b2 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -241,12 +241,12 @@
<!-- Bluetooth settings. Similar to bluetooth_profile_a2dp_high_quality, but used when the device supports high quality audio but we don't know which codec that will be used. -->
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec">HD audio</string>
- <!-- Bluetooth settings. The user-visible string that is used whenever referring to the Hearing Aid profile. -->
- <string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the Hearing aid profile. -->
+ <string name="bluetooth_profile_hearing_aid">Hearing aids</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the LE audio profile. -->
<string name="bluetooth_profile_le_audio">LE Audio</string>
- <!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
- <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the Hearing aid checkbox preference when hearing aid is connected. -->
+ <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to hearing aids</string>
<!-- Bluetooth settings. Connection options screen. The summary for the LE audio checkbox preference when LE audio is connected. -->
<string name="bluetooth_le_audio_profile_summary_connected">Connected to LE audio</string>
@@ -287,8 +287,8 @@
for the HID checkbox preference that describes how checking it
will set the HID profile as preferred. -->
<string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
- <!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference that describes how checking it will set the Hearing Aid profile as preferred. -->
- <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for Hearing Aids</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the Hearing aid checkbox preference that describes how checking it will set the hearing aid related profile as preferred. -->
+ <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for hearing aids</string>
<!-- Bluetooth settings. Connection options screen. The summary for the LE_AUDIO checkbox preference that describes how checking it will set the LE_AUDIO profile as preferred. -->
<string name="bluetooth_le_audio_profile_summary_use_for">Use for LE_AUDIO</string>
@@ -827,6 +827,11 @@
<!-- UI debug setting: show touches location summary [CHAR LIMIT=50] -->
<string name="show_touches_summary">Show visual feedback for taps</string>
+ <!-- UI debug setting: show key presses? [CHAR LIMIT=25] -->
+ <string name="show_key_presses">Show key presses</string>
+ <!-- UI debug setting: show physical key presses summary [CHAR LIMIT=50] -->
+ <string name="show_key_presses_summary">Show visual feedback for physical key presses</string>
+
<!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
<string name="show_screen_updates">Show surface updates</string>
<!-- UI debug setting: show surface updates summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index cfff519..918d696 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -7,8 +7,18 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+java_library {
+ name: "SettingsLib-search-interface",
+ visibility: ["//visibility:private"],
+ srcs: ["interface-src/**/*.java"],
+ host_supported: true,
+}
+
android_library {
name: "SettingsLib-search",
+ static_libs: [
+ "SettingsLib-search-interface",
+ ],
srcs: ["src/**/*.java"],
sdk_version: "system_current",
@@ -19,12 +29,10 @@
name: "SettingsLib-annotation-processor",
processor_class: "com.android.settingslib.search.IndexableProcessor",
static_libs: [
+ "SettingsLib-search-interface",
"javapoet",
],
- srcs: [
- "processor-src/**/*.java",
- "src/com/android/settingslib/search/SearchIndexable.java",
- ],
+ srcs: ["processor-src/**/*.java"],
java_resource_dirs: ["resources"],
}
diff --git a/packages/SettingsLib/search/common.mk b/packages/SettingsLib/search/common.mk
deleted file mode 100644
index 05226db..0000000
--- a/packages/SettingsLib/search/common.mk
+++ /dev/null
@@ -1,10 +0,0 @@
-# Include this file to generate SearchIndexableResourcesImpl
-
-LOCAL_ANNOTATION_PROCESSORS += \
- SettingsLib-annotation-processor
-
-LOCAL_ANNOTATION_PROCESSOR_CLASSES += \
- com.android.settingslib.search.IndexableProcessor
-
-LOCAL_STATIC_JAVA_LIBRARIES += \
- SettingsLib-search
diff --git a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
similarity index 92%
rename from packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
rename to packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
index 638fa3e9..174f337 100644
--- a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
+++ b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
@@ -27,7 +27,7 @@
/**
* Bitfield for the form factors this class should be considered indexable for.
* Default is {@link #ALL}.
- *
+ * <p>
* TODO: actually use this value somehow
*/
int forTarget() default ALL;
@@ -35,27 +35,27 @@
/**
* Indicates that the class should be considered indexable for Mobile.
*/
- int MOBILE = 1<<0;
+ int MOBILE = 1 << 0;
/**
* Indicates that the class should be considered indexable for TV.
*/
- int TV = 1<<1;
+ int TV = 1 << 1;
/**
* Indicates that the class should be considered indexable for Wear.
*/
- int WEAR = 1<<2;
+ int WEAR = 1 << 2;
/**
* Indicates that the class should be considered indexable for Auto.
*/
- int AUTO = 1<<3;
+ int AUTO = 1 << 3;
/**
* Indicates that the class should be considered indexable for ARC++.
*/
- int ARC = 1<<4;
+ int ARC = 1 << 4;
/**
* Indicates that the class should be considered indexable for all targets.
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index e92157e..fa43915 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -34,6 +34,7 @@
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
@@ -48,10 +49,11 @@
* subclasses.
*/
@SupportedSourceVersion(SourceVersion.RELEASE_17)
+@SupportedOptions(IndexableProcessor.PACKAGE_KEY)
@SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
public class IndexableProcessor extends AbstractProcessor {
- private static final String PACKAGE = "com.android.settingslib.search";
+ private static final String SETTINGSLIB_SEARCH_PACKAGE = "com.android.settingslib.search";
private static final String CLASS_BASE = "SearchIndexableResourcesBase";
private static final String CLASS_MOBILE = "SearchIndexableResourcesMobile";
private static final String CLASS_TV = "SearchIndexableResourcesTv";
@@ -59,6 +61,9 @@
private static final String CLASS_AUTO = "SearchIndexableResourcesAuto";
private static final String CLASS_ARC = "SearchIndexableResourcesArc";
+ static final String PACKAGE_KEY = "com.android.settingslib.search.processor.package";
+
+ private String mPackage;
private Filer mFiler;
private Messager mMessager;
private boolean mRanOnce;
@@ -72,7 +77,8 @@
}
mRanOnce = true;
- final ClassName searchIndexableData = ClassName.get(PACKAGE, "SearchIndexableData");
+ final ClassName searchIndexableData =
+ ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableData");
final FieldSpec providers = FieldSpec.builder(
ParameterizedTypeName.get(
@@ -130,7 +136,7 @@
builder = arcConstructorBuilder;
}
builder.addCode(
- "$N(new SearchIndexableData($L.class, $L"
+ "$N(new com.android.settingslib.search.SearchIndexableData($L.class, $L"
+ ".SEARCH_INDEX_DATA_PROVIDER));\n",
addIndex, className, className);
} else {
@@ -150,50 +156,51 @@
final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE)
.addModifiers(Modifier.PUBLIC)
- .addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources"))
+ .addSuperinterface(
+ ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableResources"))
.addField(providers)
.addMethod(baseConstructorBuilder.build())
.addMethod(addIndex)
.addMethod(getProviderValues)
.build();
- final JavaFile searchIndexableResourcesBase = JavaFile.builder(PACKAGE, baseClass).build();
+ final JavaFile searchIndexableResourcesBase = JavaFile.builder(mPackage, baseClass).build();
- final JavaFile searchIndexableResourcesMobile = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesMobile = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_MOBILE)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(mobileConstructorBuilder.build())
.build())
.build();
- final JavaFile searchIndexableResourcesTv = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesTv = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_TV)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(tvConstructorBuilder.build())
.build())
.build();
- final JavaFile searchIndexableResourcesWear = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesWear = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_WEAR)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(wearConstructorBuilder.build())
.build())
.build();
- final JavaFile searchIndexableResourcesAuto = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesAuto = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_AUTO)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(autoConstructorBuilder.build())
.build())
.build();
- final JavaFile searchIndexableResourcesArc = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesArc = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_ARC)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(arcConstructorBuilder.build())
.build())
.build();
@@ -214,6 +221,8 @@
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
+ mPackage = processingEnvironment.getOptions()
+ .getOrDefault(PACKAGE_KEY, SETTINGSLIB_SEARCH_PACKAGE);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fe89883..6eb2f38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -484,8 +484,9 @@
if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
synchronized (mEntriesMap) {
AppEntry entry = null;
- if (mEntriesMap.contains(userId)) {
- entry = mEntriesMap.get(userId).get(packageName);
+ HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ entry = userEntriesMap.get(packageName);
}
if (entry == null) {
ApplicationInfo info = getAppInfoLocked(packageName, userId);
@@ -735,8 +736,9 @@
private AppEntry getEntryLocked(ApplicationInfo info) {
int userId = UserHandle.getUserId(info.uid);
AppEntry entry = null;
- if (mEntriesMap.contains(userId)) {
- entry = mEntriesMap.get(userId).get(info.packageName);
+ HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ entry = userEntriesMap.get(info.packageName);
}
if (DEBUG) {
Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
@@ -752,8 +754,11 @@
Log.i(TAG, "Creating AppEntry for " + info.packageName);
}
entry = new AppEntry(mContext, info, mCurId++);
- mEntriesMap.get(userId).put(info.packageName, entry);
- mAppEntries.add(entry);
+ userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ userEntriesMap.put(info.packageName, entry);
+ mAppEntries.add(entry);
+ }
} else if (entry.info != info) {
entry.info = info;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 1d081d7..34d8148 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -804,7 +804,7 @@
}
@Test
- public void getEntry_validUserId_shouldReturnEntry() {
+ public void getEntry_hasCache_shouldReturnCacheEntry() {
mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
addApp(PKG_1, /* id= */ 1);
@@ -813,10 +813,13 @@
}
@Test
- public void getEntry_invalidUserId_shouldReturnNull() {
- mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
- addApp(PKG_1, /* id= */ 1);
+ public void getEntry_hasNoCache_shouldReturnEntry() {
+ mApplicationsState.mEntriesMap.clear();
+ ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0);
+ mApplicationsState.mApplications.add(appInfo);
+ mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false);
- assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ -1)).isNull();
+ assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
+ .isEqualTo(PKG_1);
}
}
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index aee829d..01e6bf0 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,6 +1,22 @@
{
- // Looking for unit test presubmit configuration?
- // This currently lives in ATP config apct/system_ui/unit_test
+ // Curious where your @Scenario tests will run?
+ //
+ // @Ignore: Will not run in any configuration
+ //
+ // @FlakyTest: Tests that don't block pre/postsubmit but are staged to run known failures
+ //
+ // @Postsubmit: Runs in platinum suite and blocks droidfood in postsubmit
+ //
+ // @PlatinumTest: As of May, 2023, running in postsubmit. Set to run in presubmit as part of
+ // v2/android-platinum/suite-test-mapping-platinum-sysui
+ // Please DO NOT annotate new or old tests with @PlatinumTest annotation without discussing
+ // with mdb:android-platinum
+ //
+ // As of May, 2023, If you don't use @Postsubmit, your new test will immediately
+ // block presubmit, which is probably NOT what you want. This will change effectively once
+ // we move to @PlatinumTest annotation.
+
+ // v2/sysui/test-mapping-presubmit-sysui_cloud-tf
"presubmit-sysui": [
{
"name": "PlatformScenarioTests",
@@ -23,6 +39,7 @@
]
}
],
+ // v2/android-virtual-infra/test_mapping/presubmit-avd
"presubmit": [
{
"name": "SystemUIGoogleTests",
@@ -75,21 +92,6 @@
]
}
],
-
- // Curious where your @Scenario tests will run?
- //
- // @Ignore: nowhere
- // @FlakyTest: in staged-postsubmit, but not blocking postsubmit or
- // presubmit
- // @Postsubmit: in postsubmit and staged-postsubmit, but not presubmit
- // none of the above: in presubmit, postsubmit, and staged-postsubmit
- //
- // Ideally, please annotate new tests with @FlakyTest, then with @Postsubmit
- // once they're ready for postsubmit as they will immediately block go/android-platinum,
- // then with neither once they're ready for presubmit.
- //
- // If you don't use @Postsubmit, your new test will immediately
- // block presubmit, which is probably not what you want!
"auto-end-to-end-postsubmit": [
{
"name": "AndroidAutomotiveHomeTests",
@@ -107,5 +109,27 @@
}
]
}
+ ],
+ "silver-sysui": [
+ {
+ "name": "PlatformScenarioTests",
+ "options": [
+ {
+ "include-filter": "android.platform.test.scenario.sysui"
+ },
+ {
+ "include-annotation": "android.platform.test.scenario.annotation.Scenario"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.PlatinumTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index a8febe7..efdb0a3 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -147,10 +147,12 @@
android:id="@+id/magnifier_zoom_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- app:max="6"
app:progress="0"
app:iconStartContentDescription="@string/accessibility_control_zoom_out"
- app:iconEndContentDescription="@string/accessibility_control_zoom_in"/>
+ app:iconEndContentDescription="@string/accessibility_control_zoom_in"
+ app:tickMark="@android:color/transparent"
+ app:seekBarChangeMagnitude="10"
+ />
<Button
android:id="@+id/magnifier_done_button"
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
new file mode 100644
index 0000000..b1d6a27
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":55,"w":80,"h":80,"nm":"unlocked_to_checkmark_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.143,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.761,0],[0,-2.7],[0,0]],"o":[[0,0],[0,-2.7],[-2.761,0],[0,0],[0,0]],"v":[[5,5],[5,-0.111],[0,-5],[-5,-0.111],[-5.01,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[38,45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.999,44.999,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.42,0],[0,1.42],[1.42,0],[0,-1.42]],"o":[[1.42,0],[0,-1.42],[-1.42,0],[0,1.42]],"v":[[0,2.571],[2.571,0],[0,-2.571],[-2.571,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.5,40.75,0],"ix":2,"l":2},"a":{"a":0,"k":[12.5,-6.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":19,"s":[112,112,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":77,"st":10,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 5efcef3..99311e3 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -100,4 +100,8 @@
<color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
<color name="people_tile_background">@color/material_dynamic_secondary20</color>
+
+ <!-- Internet Dialog -->
+ <color name="connected_network_primary_color">@color/material_dynamic_primary80</color>
+ <color name="connected_network_secondary_color">@color/material_dynamic_secondary80</color>
</resources>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 99bc794..b6971d3 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -58,17 +58,13 @@
<style name="TextAppearance.InternetDialog.Active">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textSize">16sp</item>
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+ <item name="android:textColor">@color/material_dynamic_primary80</item>
<item name="android:textDirection">locale</item>
</style>
<style name="TextAppearance.InternetDialog.Secondary.Active">
<item name="android:textSize">14sp</item>
- <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
- </style>
-
- <style name="InternetDialog.Divider.Active">
- <item name="android:background">?android:attr/textColorSecondaryInverse</item>
+ <item name="android:textColor">@color/material_dynamic_secondary80</item>
</style>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index bd86e51..3a1d1a8 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -216,6 +216,8 @@
<attr name="progress" format="integer" />
<attr name="iconStartContentDescription" format="reference" />
<attr name="iconEndContentDescription" format="reference" />
+ <attr name="tickMark" format="reference" />
+ <attr name="seekBarChangeMagnitude" format="integer" />
</declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c619d46..ab789d9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -613,8 +613,8 @@
<string name="quick_settings_bluetooth_secondary_label_headset">Headset</string>
<!-- QuickSettings: Bluetooth secondary label for an input/IO device being connected [CHAR LIMIT=20]-->
<string name="quick_settings_bluetooth_secondary_label_input">Input</string>
- <!-- QuickSettings: Bluetooth secondary label for a Hearing Aids device being connected [CHAR LIMIT=20]-->
- <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing Aids</string>
+ <!-- QuickSettings: Bluetooth secondary label for a Hearing aids device being connected [CHAR LIMIT=20]-->
+ <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing aids</string>
<!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_secondary_label_transient">Turning on…</string>
<!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index cee2135..cb5342a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1183,12 +1183,6 @@
<style name="TextAppearance.InternetDialog.Secondary.Active"/>
- <style name="InternetDialog.Divider">
- <item name="android:background">?android:attr/textColorSecondary</item>
- </style>
-
- <style name="InternetDialog.Divider.Active"/>
-
<style name="FgsManagerDialogTitle">
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textStyle">bold</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 4b02171..4211f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1226,15 +1226,16 @@
constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
constraintSet.applyTo(mView);
} else {
- int leftElement = leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId();
- int rightElement =
+ int startElement =
+ leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId();
+ int endElement =
leftAlign ? mUserSwitcherViewGroup.getId() : mViewFlipper.getId();
ConstraintSet constraintSet = new ConstraintSet();
- constraintSet.connect(leftElement, LEFT, PARENT_ID, LEFT);
- constraintSet.connect(leftElement, RIGHT, rightElement, LEFT);
- constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
- constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
+ constraintSet.connect(startElement, START, PARENT_ID, START);
+ constraintSet.connect(startElement, END, endElement, START);
+ constraintSet.connect(endElement, START, startElement, END);
+ constraintSet.connect(endElement, END, PARENT_ID, END);
constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM);
constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 99d4662..c30a214 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -20,6 +20,7 @@
import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
+import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -54,9 +55,10 @@
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.animation.PhysicsAnimator.SpringConfig;
+import java.io.PrintWriter;
import java.util.function.Consumer;
-public class SwipeHelper implements Gefingerpoken {
+public class SwipeHelper implements Gefingerpoken, Dumpable {
static final String TAG = "com.android.systemui.SwipeHelper";
private static final boolean DEBUG_INVALIDATE = false;
private static final boolean CONSTRAIN_SWIPE = true;
@@ -844,6 +846,31 @@
return false;
}
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.append("mTouchedView=").print(mTouchedView);
+ if (mTouchedView instanceof ExpandableNotificationRow) {
+ pw.append(" key=").println(logKey((ExpandableNotificationRow) mTouchedView));
+ } else {
+ pw.println();
+ }
+ pw.append("mIsSwiping=").println(mIsSwiping);
+ pw.append("mSnappingChild=").println(mSnappingChild);
+ pw.append("mLongPressSent=").println(mLongPressSent);
+ pw.append("mInitialTouchPos=").println(mInitialTouchPos);
+ pw.append("mTranslation=").println(mTranslation);
+ pw.append("mCanCurrViewBeDimissed=").println(mCanCurrViewBeDimissed);
+ pw.append("mMenuRowIntercepting=").println(mMenuRowIntercepting);
+ pw.append("mDisableHwLayers=").println(mDisableHwLayers);
+ pw.append("mDismissPendingMap: ").println(mDismissPendingMap.size());
+ if (!mDismissPendingMap.isEmpty()) {
+ mDismissPendingMap.forEach((view, animator) -> {
+ pw.append(" ").print(view);
+ pw.append(": ").println(animator);
+ });
+ }
+ }
+
public interface Callback {
View getChildAtPosition(MotionEvent ev);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 7c76588..e7eab7e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -76,6 +76,7 @@
import androidx.core.math.MathUtils;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
@@ -101,7 +102,9 @@
// Delay to avoid updating state description too frequently.
private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100;
// It should be consistent with the value defined in WindowMagnificationGestureHandler.
- private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(1.0f, 8.0f);
+ private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(
+ MagnificationConstants.SCALE_MIN_VALUE,
+ MagnificationConstants.SCALE_MAX_VALUE);
private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
private final SparseArray<Float> mMagnificationSizeScaleOptions = new SparseArray<>();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index d27087d..b086912 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -22,6 +22,9 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
import android.annotation.IntDef;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -87,7 +90,8 @@
private final MagnificationGestureDetector mGestureDetector;
private boolean mSingleTapDetected = false;
- private SeekBarWithIconButtonsView mZoomSeekbar;
+ @VisibleForTesting
+ SeekBarWithIconButtonsView mZoomSeekbar;
private LinearLayout mAllowDiagonalScrollingView;
private TextView mAllowDiagonalScrollingTitle;
private Switch mAllowDiagonalScrollingSwitch;
@@ -102,11 +106,15 @@
private ImageButton mFullScreenButton;
private int mLastSelectedButtonIndex = MagnificationSize.NONE;
private boolean mAllowDiagonalScrolling = false;
- private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
- private static final float A11Y_SCALE_MIN_VALUE = 1.0f;
+ /**
+ * Amount by which magnification scale changes compared to seekbar in settings.
+ * magnitude = 10 means, for every 1 scale increase, 10 progress increase in seekbar.
+ */
+ private int mSeekBarMagnitude;
private WindowMagnificationSettingsCallback mCallback;
private ContentObserver mMagnificationCapabilityObserver;
+ private ContentObserver mMagnificationScaleObserver;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -156,18 +164,26 @@
});
}
};
+ mMagnificationScaleObserver = new ContentObserver(
+ mContext.getMainThreadHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ setScaleSeekbar(getMagnificationScale());
+ }
+ };
}
private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
+ float scale = (progress / (float) mSeekBarMagnitude) + SCALE_MIN_VALUE;
// Update persisted scale only when scale >= PERSISTED_SCALE_MIN_VALUE const.
// We assume if the scale is lower than the PERSISTED_SCALE_MIN_VALUE, there will be
// no obvious magnification effect.
if (scale >= MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
- Settings.Secure.putFloatForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
+ mSecureSettings.putFloatForUser(
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ scale,
UserHandle.USER_CURRENT);
}
mCallback.onMagnifierScale(scale);
@@ -301,6 +317,7 @@
// Unregister observer before removing view
mSecureSettings.unregisterContentObserver(mMagnificationCapabilityObserver);
+ mSecureSettings.unregisterContentObserver(mMagnificationScaleObserver);
mWindowManager.removeView(mSettingView);
mIsVisible = false;
if (resetPosition) {
@@ -329,7 +346,13 @@
}
public void setScaleSeekbar(float scale) {
- setSeekbarProgress(scale);
+ int index = (int) ((scale - SCALE_MIN_VALUE) * mSeekBarMagnitude);
+ if (index < 0) {
+ index = 0;
+ } else if (index > mZoomSeekbar.getMax()) {
+ index = mZoomSeekbar.getMax();
+ }
+ mZoomSeekbar.setProgress(index);
}
private void transitToMagnificationMode(int mode) {
@@ -346,6 +369,7 @@
private void showSettingPanel(boolean resetPosition) {
if (!mIsVisible) {
updateUIControlsIfNeeded();
+ setScaleSeekbar(getMagnificationScale());
if (resetPosition) {
mDraggableWindowBounds.set(getDraggableWindowBounds());
mParams.x = mDraggableWindowBounds.right;
@@ -358,6 +382,10 @@
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
mMagnificationCapabilityObserver,
UserHandle.USER_CURRENT);
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ mMagnificationScaleObserver,
+ UserHandle.USER_CURRENT);
// Exclude magnification switch button from system gesture area.
setSystemGestureExclusion();
@@ -390,9 +418,15 @@
UserHandle.USER_CURRENT);
}
+ private float getMagnificationScale() {
+ return mSecureSettings.getFloatForUser(
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ SCALE_MIN_VALUE,
+ UserHandle.USER_CURRENT);
+ }
+
private void updateUIControlsIfNeeded() {
int capability = getMagnificationCapability();
-
int selectedButtonIndex = mLastSelectedButtonIndex;
switch (capability) {
case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
@@ -453,14 +487,6 @@
}
};
- private void setSeekbarProgress(float scale) {
- int index = (int) ((scale - A11Y_SCALE_MIN_VALUE) / A11Y_CHANGE_SCALE_DIFFERENCE);
- if (index < 0) {
- index = 0;
- }
- mZoomSeekbar.setProgress(index);
- }
-
void inflateView() {
mSettingView = (LinearLayout) View.inflate(mContext,
R.layout.window_magnification_settings_view, null);
@@ -482,10 +508,13 @@
mSettingView.findViewById(R.id.magnifier_horizontal_lock_title);
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
+ mZoomSeekbar.setMax((int) (mZoomSeekbar.getChangeMagnitude()
+ * (SCALE_MAX_VALUE - SCALE_MIN_VALUE)));
+ mSeekBarMagnitude = mZoomSeekbar.getChangeMagnitude();
float scale = mSecureSettings.getFloatForUser(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
UserHandle.USER_CURRENT);
- setSeekbarProgress(scale);
+ setScaleSeekbar(scale);
mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
mAllowDiagonalScrollingView =
@@ -601,7 +630,7 @@
@VisibleForTesting
void setDiagonalScrolling(boolean enabled) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ mSecureSettings.putIntForUser(
Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 1 : 0,
UserHandle.USER_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 682888f..95610ae 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -21,6 +21,7 @@
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
@@ -47,13 +48,16 @@
@BiometricState oldState: Int,
@BiometricState newState: Int
): Int? = when (newState) {
+ STATE_AUTHENTICATED -> {
+ if (oldState == STATE_PENDING_CONFIRMATION) {
+ R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+ } else {
+ super.getAnimationForTransition(oldState, newState)
+ }
+ }
STATE_PENDING_CONFIRMATION -> {
if (oldState == STATE_ERROR || oldState == STATE_HELP) {
R.raw.fingerprint_dialogue_error_to_unlock_lottie
- } else if (oldState == STATE_PENDING_CONFIRMATION) {
- // TODO(jbolinger): missing asset for this transition
- // (unlocked icon to success checkmark)
- R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
} else {
R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 630cfff..baaa96e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -15,8 +15,6 @@
*/
package com.android.systemui.biometrics
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.app.ActivityTaskManager
import android.content.Context
import android.content.res.Configuration
@@ -43,7 +41,6 @@
import android.view.View
import android.view.View.AccessibilityDelegate
import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
@@ -61,7 +58,6 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
-import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
import java.io.PrintWriter
@@ -83,7 +79,6 @@
fingerprintManager: FingerprintManager?,
private val windowManager: WindowManager,
private val activityTaskManager: ActivityTaskManager,
- overviewProxyService: OverviewProxyService,
displayManager: DisplayManager,
private val displayStateInteractor: DisplayStateInteractor,
@Main private val mainExecutor: DelayableExecutor,
@@ -112,19 +107,6 @@
@VisibleForTesting val orientationListener = orientationReasonListener.orientationListener
- @VisibleForTesting
- val overviewProxyListener =
- object : OverviewProxyService.OverviewProxyListener {
- override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
- overlayView?.let { view ->
- handler.postDelayed({ updateOverlayVisibility(view) }, 500)
- }
- }
- }
-
- private val animationDuration =
- context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
-
private val isReverseDefaultRotation =
context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
@@ -142,7 +124,6 @@
field = value
field?.let { newView ->
windowManager.addView(newView, overlayViewParams)
- updateOverlayVisibility(newView)
orientationListener.enable()
}
}
@@ -181,7 +162,6 @@
override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
}
)
- overviewProxyService.addCallback(overviewProxyListener)
listenForAlternateBouncerVisibility()
dumpManager.registerDumpable(this)
@@ -340,45 +320,6 @@
windowManager.updateViewLayout(overlayView, overlayViewParams)
}
- private fun updateOverlayVisibility(view: View) {
- if (view != overlayView) {
- return
- }
- // hide after a few seconds if the sensor is oriented down and there are
- // large overlapping system bars
- var rotation = context.display?.rotation
-
- if (rotation != null) {
- rotation = getRotationFromDefault(rotation)
- }
-
- if (
- windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
- ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
- (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))
- ) {
- overlayHideAnimator =
- view
- .animate()
- .alpha(0f)
- .setStartDelay(3_000)
- .setDuration(animationDuration)
- .setListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- view.visibility = View.GONE
- overlayHideAnimator = null
- }
- }
- )
- } else {
- overlayHideAnimator?.cancel()
- overlayHideAnimator = null
- view.alpha = 1f
- view.visibility = View.VISIBLE
- }
- }
-
private fun getRotationFromDefault(rotation: Int): Int =
if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
}
@@ -426,9 +367,6 @@
private fun Display.isNaturalOrientation(): Boolean =
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-private fun WindowInsets.hasBigNavigationBar(): Boolean =
- getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
-
private fun LottieAnimationView.addOverlayDynamicColor(
context: Context,
@BiometricOverlayConstants.ShowReason reason: Int
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index de3a990..4538a6c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -27,6 +27,7 @@
import android.widget.LinearLayout;
import android.widget.SeekBar;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
/**
@@ -37,12 +38,14 @@
private static final int DEFAULT_SEEKBAR_MAX = 6;
private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
+ private static final int DEFAULT_SEEKBAR_TICK_MARK = 0;
private ViewGroup mIconStartFrame;
private ViewGroup mIconEndFrame;
private ImageView mIconStart;
private ImageView mIconEnd;
private SeekBar mSeekbar;
+ private int mSeekBarChangeMagnitude = 1;
private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
private String[] mStateLabels = null;
@@ -102,6 +105,15 @@
context.getString(iconEndFrameContentDescriptionId);
mIconEndFrame.setContentDescription(contentDescription);
}
+ int tickMarkId = typedArray.getResourceId(
+ R.styleable.SeekBarWithIconButtonsView_Layout_tickMark,
+ DEFAULT_SEEKBAR_TICK_MARK);
+ if (tickMarkId != DEFAULT_SEEKBAR_TICK_MARK) {
+ mSeekbar.setTickMark(getResources().getDrawable(tickMarkId));
+ }
+ mSeekBarChangeMagnitude = typedArray.getInt(
+ R.styleable.SeekBarWithIconButtonsView_Layout_seekBarChangeMagnitude,
+ /* defValue= */ 1);
} else {
mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
setProgress(DEFAULT_SEEKBAR_PROGRESS);
@@ -112,7 +124,7 @@
mIconStartFrame.setOnClickListener((view) -> {
final int progress = mSeekbar.getProgress();
if (progress > 0) {
- mSeekbar.setProgress(progress - 1);
+ mSeekbar.setProgress(progress - mSeekBarChangeMagnitude);
setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0);
}
});
@@ -120,7 +132,7 @@
mIconEndFrame.setOnClickListener((view) -> {
final int progress = mSeekbar.getProgress();
if (progress < mSeekbar.getMax()) {
- mSeekbar.setProgress(progress + 1);
+ mSeekbar.setProgress(progress + mSeekBarChangeMagnitude);
setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
}
});
@@ -183,6 +195,20 @@
}
/**
+ * Gets max to the seekbar in the layout.
+ */
+ public int getMax() {
+ return mSeekbar.getMax();
+ }
+
+ /**
+ * @return the magnitude by which seekbar progress changes when start and end icons are clicked.
+ */
+ public int getChangeMagnitude() {
+ return mSeekBarChangeMagnitude;
+ }
+
+ /**
* Sets progress to the seekbar in the layout.
* If the progress is smaller than or equals to 0, the IconStart will be disabled. If the
* progress is larger than or equals to Max, the IconEnd will be disabled. The seekbar progress
@@ -193,6 +219,11 @@
updateIconViewIfNeeded(progress);
}
+ @VisibleForTesting
+ public int getProgress() {
+ return mSeekbar.getProgress();
+ }
+
private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index b141db1..c2421dc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -19,7 +19,6 @@
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
import android.service.dreams.DreamService;
-import android.util.Log;
import androidx.annotation.NonNull;
@@ -52,7 +51,6 @@
public class DreamOverlayStateController implements
CallbackController<DreamOverlayStateController.Callback> {
private static final String TAG = "DreamOverlayStateCtlr";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
@@ -110,13 +108,17 @@
private final int mSupportedTypes;
+ private final DreamLogger mLogger;
+
@VisibleForTesting
@Inject
public DreamOverlayStateController(@Main Executor executor,
@Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ DreamLogger dreamLogger) {
mExecutor = executor;
mOverlayEnabled = overlayEnabled;
+ mLogger = dreamLogger;
mFeatureFlags = featureFlags;
if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
@@ -124,9 +126,7 @@
} else {
mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
}
- if (DEBUG) {
- Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
- }
+ mLogger.d(TAG, "Dream overlay enabled: " + mOverlayEnabled);
}
/**
@@ -134,18 +134,14 @@
*/
public void addComplication(Complication complication) {
if (!mOverlayEnabled) {
- if (DEBUG) {
- Log.d(TAG,
- "Ignoring adding complication due to overlay disabled:" + complication);
- }
+ mLogger.d(TAG,
+ "Ignoring adding complication due to overlay disabled: " + complication);
return;
}
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
- if (DEBUG) {
- Log.d(TAG, "addComplication: added " + complication);
- }
+ mLogger.d(TAG, "Added dream complication: " + complication);
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -156,18 +152,14 @@
*/
public void removeComplication(Complication complication) {
if (!mOverlayEnabled) {
- if (DEBUG) {
- Log.d(TAG,
- "Ignoring removing complication due to overlay disabled:" + complication);
- }
+ mLogger.d(TAG,
+ "Ignoring removing complication due to overlay disabled: " + complication);
return;
}
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
- if (DEBUG) {
- Log.d(TAG, "removeComplication: removed " + complication);
- }
+ mLogger.d(TAG, "Removed dream complication: " + complication);
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -313,6 +305,7 @@
* @param active {@code true} if overlay is active, {@code false} otherwise.
*/
public void setOverlayActive(boolean active) {
+ mLogger.d(TAG, "Dream overlay active: " + active);
modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
}
@@ -321,6 +314,8 @@
* @param active {@code true} if low light mode is active, {@code false} otherwise.
*/
public void setLowLightActive(boolean active) {
+ mLogger.d(TAG, "Low light mode active: " + active);
+
if (isLowLightActive() && !active) {
// Notify that we're exiting low light only on the transition from active to not active.
mCallbacks.forEach(Callback::onExitLowLight);
@@ -351,6 +346,7 @@
* @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
*/
public void setHasAssistantAttention(boolean hasAttention) {
+ mLogger.d(TAG, "Dream overlay has Assistant attention: " + hasAttention);
modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
}
@@ -359,6 +355,7 @@
* @param visible {@code true} if the status bar is visible, {@code false} otherwise.
*/
public void setDreamOverlayStatusBarVisible(boolean visible) {
+ mLogger.d(TAG, "Dream overlay status bar visible: " + visible);
modifyState(
visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
}
@@ -376,6 +373,7 @@
*/
public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
mExecutor.execute(() -> {
+ mLogger.d(TAG, "Available complication types: " + types);
mAvailableComplicationTypes = types;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
@@ -393,6 +391,7 @@
*/
public void setShouldShowComplications(boolean shouldShowComplications) {
mExecutor.execute(() -> {
+ mLogger.d(TAG, "Should show complications: " + shouldShowComplications);
mShouldShowComplications = shouldShowComplications;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 7c1bfed..1865c38 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -141,6 +141,20 @@
mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
}
+ protected static String getLoggableStatusIconType(@StatusIconType int type) {
+ return switch (type) {
+ case STATUS_ICON_NOTIFICATIONS -> "notifications";
+ case STATUS_ICON_WIFI_UNAVAILABLE -> "wifi_unavailable";
+ case STATUS_ICON_ALARM_SET -> "alarm_set";
+ case STATUS_ICON_CAMERA_DISABLED -> "camera_disabled";
+ case STATUS_ICON_MIC_DISABLED -> "mic_disabled";
+ case STATUS_ICON_MIC_CAMERA_DISABLED -> "mic_camera_disabled";
+ case STATUS_ICON_PRIORITY_MODE_ON -> "priority_mode_on";
+ case STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE -> "assistant_attention_active";
+ default -> type + "(unknown)";
+ };
+ }
+
void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
View icon = mStatusIcons.get(iconType);
if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index c954f98..3a28408 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -61,6 +61,8 @@
*/
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+ private static final String TAG = "DreamStatusBarCtrl";
+
private final ConnectivityManager mConnectivityManager;
private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
private final NextAlarmController mNextAlarmController;
@@ -78,6 +80,7 @@
private final Executor mMainExecutor;
private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
new ArrayList<>();
+ private final DreamLogger mLogger;
private boolean mIsAttached;
@@ -157,7 +160,8 @@
StatusBarWindowStateController statusBarWindowStateController,
DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
DreamOverlayStateController dreamOverlayStateController,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ DreamLogger dreamLogger) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
@@ -173,6 +177,7 @@
mZenModeController = zenModeController;
mDreamOverlayStateController = dreamOverlayStateController;
mUserTracker = userTracker;
+ mLogger = dreamLogger;
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -341,6 +346,8 @@
@Nullable String contentDescription) {
mMainExecutor.execute(() -> {
if (mIsAttached) {
+ mLogger.d(TAG, (show ? "Showing" : "Hiding") + " dream status bar item: "
+ + DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
mView.showIcon(iconType, show, contentDescription);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 29a7fe7..c706363 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -33,10 +33,10 @@
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.math.MathUtils
+import com.android.app.animation.Interpolators
import com.android.internal.R
import com.android.keyguard.KeyguardClockSwitchController
import com.android.keyguard.KeyguardViewController
-import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -433,7 +433,14 @@
// animate state.
if (!keyguardStateController.isKeyguardGoingAway &&
willUnlockWithInWindowLauncherAnimations) {
- launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+ try {
+ launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */)
+ } catch (e: DeadObjectException) {
+ Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
+ "onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
+ "Launcher is in the process of being destroyed, but the IPC to System UI " +
+ "telling us hasn't arrived yet.")
+ }
}
}
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 742e535..81f62b6 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
@@ -25,6 +25,7 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
@@ -44,13 +45,16 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
/** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository {
@@ -138,7 +142,7 @@
val statusBarState: Flow<StatusBarState>
/** Observable for device wake/sleep state */
- val wakefulness: Flow<WakefulnessModel>
+ val wakefulness: StateFlow<WakefulnessModel>
/** Observable for biometric unlock modes */
val biometricUnlockState: Flow<BiometricUnlockModel>
@@ -202,7 +206,8 @@
private val dozeParameters: DozeParameters,
private val authController: AuthController,
private val dreamOverlayCallbackController: DreamOverlayCallbackController,
- @Main private val mainDispatcher: CoroutineDispatcher
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -486,47 +491,48 @@
awaitClose { biometricUnlockController.removeListener(callback) }
}
- override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onStartedWakingUp() {
- dispatchNewState()
- }
+ override val wakefulness: StateFlow<WakefulnessModel> =
+ conflatedCallbackFlow {
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onStartedWakingUp() {
+ dispatchNewState()
+ }
- override fun onFinishedWakingUp() {
- dispatchNewState()
- }
+ override fun onFinishedWakingUp() {
+ dispatchNewState()
+ }
- override fun onPostFinishedWakingUp() {
- dispatchNewState()
- }
+ override fun onPostFinishedWakingUp() {
+ dispatchNewState()
+ }
- override fun onStartedGoingToSleep() {
- dispatchNewState()
- }
+ override fun onStartedGoingToSleep() {
+ dispatchNewState()
+ }
- override fun onFinishedGoingToSleep() {
- dispatchNewState()
- }
+ override fun onFinishedGoingToSleep() {
+ dispatchNewState()
+ }
- private fun dispatchNewState() {
- trySendWithFailureLogging(
- WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
- TAG,
- "updated wakefulness state"
- )
- }
+ private fun dispatchNewState() {
+ trySendWithFailureLogging(
+ WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+ TAG,
+ "updated wakefulness state",
+ )
+ }
+ }
+
+ wakefulnessLifecycle.addObserver(observer)
+ awaitClose { wakefulnessLifecycle.removeObserver(observer) }
}
-
- wakefulnessLifecycle.addObserver(observer)
- trySendWithFailureLogging(
- WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
- TAG,
- "initial wakefulness state"
- )
-
- awaitClose { wakefulnessLifecycle.removeObserver(observer) }
- }
+ .stateIn(
+ scope,
+ // Use Eagerly so that we're always listening and never miss an event.
+ SharingStarted.Eagerly,
+ initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
+ )
override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
fun sendFpLocation() {
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 f99b8a2..d13d5ad 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
@@ -40,6 +40,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -99,7 +100,7 @@
}
/** The device wake/sleep state */
- val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
+ val wakefulnessModel: StateFlow<WakefulnessModel> = repository.wakefulness
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
index 51ce7ff..fb685da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -23,9 +23,12 @@
/** The physical power button was pressed to wake up or sleep the device. */
POWER_BUTTON,
- /** The user has taped or double tapped to wake the screen */
+ /** The user has tapped or double tapped to wake the screen. */
TAP,
+ /** The user performed some sort of gesture to wake the screen. */
+ GESTURE,
+
/** Something else happened to wake up or sleep the device. */
OTHER;
@@ -34,6 +37,7 @@
return when (reason) {
PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
PowerManager.WAKE_REASON_TAP -> TAP
+ PowerManager.WAKE_REASON_GESTURE -> GESTURE
else -> OTHER
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 7ca90ba..dd57713 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -27,7 +27,11 @@
fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP
- fun isStartingToSleepOrAsleep() = isStartingToSleep() || state == WakefulnessState.ASLEEP
+ private fun isAsleep() = state == WakefulnessState.ASLEEP
+
+ fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep()
+
+ fun isDeviceInteractive() = !isAsleep()
fun isStartingToSleepFromPowerButton() =
isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON
@@ -41,6 +45,11 @@
fun isAwakeFromTap() =
state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP
+ fun isDeviceInteractiveFromTapOrGesture(): Boolean {
+ return isDeviceInteractive() &&
+ (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
+ }
+
companion object {
fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
return WakefulnessModel(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index e38abc2..6eaff3f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -56,6 +56,7 @@
R.dimen.qs_media_session_disabled_seekbar_vertical_padding
)
var seekBarResetAnimator: Animator? = null
+ var animationEnabled: Boolean = true
init {
val seekBarProgressWavelength =
@@ -104,7 +105,7 @@
holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
holder.seekBar.isEnabled = data.seekAvailable
- progressDrawable?.animate = data.playing && !data.scrubbing
+ progressDrawable?.animate = data.playing && !data.scrubbing && animationEnabled
progressDrawable?.transitionEnabled = !data.seekAvailable
if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 32a7935..516fbf5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -34,6 +34,7 @@
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Color;
@@ -54,6 +55,7 @@
import android.media.session.PlaybackState;
import android.os.Process;
import android.os.Trace;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -121,6 +123,7 @@
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
@@ -244,11 +247,19 @@
private MultiRippleController mMultiRippleController;
private TurbulenceNoiseController mTurbulenceNoiseController;
private final FeatureFlags mFeatureFlags;
+ private final GlobalSettings mGlobalSettings;
// TODO(b/281032715): Consider making this as a final variable. For now having a null check
// due to unit test failure. (Perhaps missing some setup)
private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig;
+ private ContentObserver mAnimationScaleObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateAnimatorDurationScale();
+ }
+ };
+
/**
* Initialize a new control panel
*
@@ -276,7 +287,8 @@
ActivityIntentHelper activityIntentHelper,
NotificationLockscreenUserManager lockscreenUserManager,
BroadcastDialogController broadcastDialogController,
- FeatureFlags featureFlags
+ FeatureFlags featureFlags,
+ GlobalSettings globalSettings
) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
@@ -305,6 +317,13 @@
});
mFeatureFlags = featureFlags;
+
+ mGlobalSettings = globalSettings;
+ mGlobalSettings.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+ mAnimationScaleObserver
+ );
+ updateAnimatorDurationScale();
}
/**
@@ -387,6 +406,13 @@
updateSeekBarVisibility();
}
+ private void updateAnimatorDurationScale() {
+ if (mSeekBarObserver != null) {
+ mSeekBarObserver.setAnimationEnabled(
+ mGlobalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f);
+ }
+ }
+
/**
* Get the context
*
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 cfb581b..cb8d87a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -20,9 +20,8 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadFourFingerSwipe;
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMultiFingerSwipe;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -989,7 +988,7 @@
private void onMotionEvent(MotionEvent ev) {
int action = ev.getActionMasked();
- boolean isTrackpadMultiFingerSwipe = isTrackpadMultiFingerSwipe(
+ boolean isTrackpadThreeFingerSwipe = isTrackpadThreeFingerSwipe(
mIsTrackpadGestureFeaturesEnabled, ev);
if (action == MotionEvent.ACTION_DOWN) {
if (DEBUG_MISSING_GESTURE) {
@@ -1003,7 +1002,7 @@
// Verify if this is in within the touch region and we aren't in immersive mode, and
// either the bouncer is showing or the notification panel is hidden
mInputEventReceiver.setBatchingEnabled(false);
- if (isTrackpadMultiFingerSwipe) {
+ if (isTrackpadThreeFingerSwipe) {
// Since trackpad gestures don't have zones, this will be determined later by the
// direction of the gesture. {@code mIsOnLeftEdge} is set to false to begin with.
mDeferSetIsOnLeftEdge = true;
@@ -1019,11 +1018,11 @@
&& !mGestureBlockingActivityRunning
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
&& !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
- if (isTrackpadMultiFingerSwipe) {
+ if (isTrackpadThreeFingerSwipe) {
// Trackpad back gestures don't have zones, so we don't need to check if the down
// event is within insets.
mAllowGesture = isBackAllowedCommon && isValidTrackpadBackGesture(
- isTrackpadMultiFingerSwipe);
+ isTrackpadThreeFingerSwipe);
} else {
mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
&& isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
@@ -1034,7 +1033,7 @@
mEdgeBackPlugin.onMotionEvent(ev);
dispatchToBackAnimation(ev);
}
- if (mLogGesture || isTrackpadMultiFingerSwipe) {
+ if (mLogGesture || isTrackpadThreeFingerSwipe) {
mDownPoint.set(ev.getX(), ev.getY());
mEndPoint.set(-1, -1);
mThresholdCrossed = false;
@@ -1045,10 +1044,10 @@
mTmpLogDate.setTime(curTime);
String curTimeStr = mLogDateFormat.format(mTmpLogDate);
(isWithinInsets ? mGestureLogInsideInsets : mGestureLogOutsideInsets).log(String.format(
- "Gesture [%d [%s],alw=%B, mltf=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
+ "Gesture [%d [%s],alw=%B, t3fs=%B, left=%B, defLeft=%B, backAlw=%B, disbld=%B,"
+ " qsDisbld=%b, blkdAct=%B, pip=%B,"
+ " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]",
- curTime, curTimeStr, mAllowGesture, isTrackpadMultiFingerSwipe,
+ curTime, curTimeStr, mAllowGesture, isTrackpadThreeFingerSwipe,
mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep,
mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
@@ -1057,8 +1056,7 @@
if (!mThresholdCrossed) {
mEndPoint.x = (int) ev.getX();
mEndPoint.y = (int) ev.getY();
- if (action == MotionEvent.ACTION_POINTER_DOWN && (!isTrackpadMultiFingerSwipe
- || isTrackpadFourFingerSwipe(mIsTrackpadGestureFeaturesEnabled, ev))) {
+ if (action == MotionEvent.ACTION_POINTER_DOWN && !isTrackpadThreeFingerSwipe) {
if (mAllowGesture) {
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
if (DEBUG_MISSING_GESTURE) {
@@ -1070,11 +1068,7 @@
mLogGesture = false;
return;
} else if (action == MotionEvent.ACTION_MOVE) {
- if (isTrackpadFourFingerSwipe(isTrackpadMultiFingerSwipe, ev)) {
- cancelGesture(ev);
- return;
- }
- if (isTrackpadMultiFingerSwipe && mDeferSetIsOnLeftEdge) {
+ if (isTrackpadThreeFingerSwipe && mDeferSetIsOnLeftEdge) {
// mIsOnLeftEdge is determined by the relative position between the down
// and the current motion event for trackpad gestures instead of zoning.
mIsOnLeftEdge = mEndPoint.x > mDownPoint.x;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index bf5e442..10a88c8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -16,6 +16,7 @@
package com.android.systemui.navigationbar.gestural;
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
@@ -29,21 +30,10 @@
&& event.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE;
}
- public static boolean isTrackpadMultiFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
- MotionEvent event) {
- return isTrackpadGestureFeaturesEnabled
- && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
- }
-
public static boolean isTrackpadThreeFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
MotionEvent event) {
- return isTrackpadMultiFingerSwipe(isTrackpadGestureFeaturesEnabled, event)
- && event.getPointerCount() == 3;
- }
-
- public static boolean isTrackpadFourFingerSwipe(boolean isTrackpadGestureFeaturesEnabled,
- MotionEvent event) {
- return isTrackpadMultiFingerSwipe(isTrackpadGestureFeaturesEnabled, event)
- && event.getPointerCount() == 4;
+ return isTrackpadGestureFeaturesEnabled
+ && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+ && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 212a2b7..6e1ef91 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -20,7 +20,6 @@
import android.app.AlertDialog;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -58,7 +57,6 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.Utils;
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -416,14 +414,6 @@
});
});
- TypedArray array = mContext.obtainStyledAttributes(
- R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background});
- int dividerColor = Utils.getColorAttrDefaultColor(mContext,
- android.R.attr.textColorSecondary);
- mMobileToggleDivider.setBackgroundColor(isNetworkConnected
- ? array.getColor(0, dividerColor) : dividerColor);
- array.recycle();
-
mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
mMobileToggleDivider.setVisibility(
mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1174d3d..127c415 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,8 +17,6 @@
package com.android.systemui.shade;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
-import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@@ -30,6 +28,8 @@
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
+import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -134,6 +134,7 @@
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
@@ -2150,10 +2151,14 @@
}
int getFalsingThreshold() {
- float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+ float factor = ShadeViewController.getFalsingThresholdFactor(getWakefulness());
return (int) (mQsController.getFalsingThreshold() * factor);
}
+ private WakefulnessModel getWakefulness() {
+ return mKeyguardInteractor.getWakefulnessModel().getValue();
+ }
+
private void maybeAnimateBottomAreaAlpha() {
mBottomAreaShadeAlphaAnimator.cancel();
if (mBarState == StatusBarState.SHADE_LOCKED) {
@@ -3587,8 +3592,10 @@
expand = flingExpands(vel, vectorVel, x, y);
}
- mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mCentralSurfaces.isWakeUpComingFromTouch());
+ mDozeLog.traceFling(
+ expand,
+ mTouchAboveFalsingThreshold,
+ /* screenOnFromTouch=*/ getWakefulness().isDeviceInteractiveFromTapOrGesture());
// Log collapse gesture if on lock screen.
if (!expand && onKeyguard) {
float displayDensity = mCentralSurfaces.getDisplayDensity();
@@ -4655,6 +4662,9 @@
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
boolean canCollapsePanel = canCollapsePanelOnTouch();
+ final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
+ mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
+ mTrackpadGestureFeaturesEnabled, event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -4687,7 +4697,7 @@
addMovement(event);
break;
case MotionEvent.ACTION_POINTER_UP:
- if (isTrackpadMotionEvent(event)) {
+ if (isTrackpadTwoOrThreeFingerSwipe) {
break;
}
final int upPointer = event.getPointerId(event.getActionIndex());
@@ -4703,7 +4713,7 @@
mShadeLog.logMotionEventStatusBarState(event,
mStatusBarStateController.getState(),
"onInterceptTouchEvent: pointer down action");
- if (!isTrackpadMotionEvent(event)
+ if (!isTrackpadTwoOrThreeFingerSwipe
&& mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
mMotionAborted = true;
mVelocityTracker.clear();
@@ -4852,9 +4862,13 @@
return false;
}
+ final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll(
+ mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe(
+ mTrackpadGestureFeaturesEnabled, event);
+
// On expanding, single mouse click expands the panel instead of dragging.
if (isFullyCollapsed() && (event.isFromSource(InputDevice.SOURCE_MOUSE)
- && !isTrackpadMotionEvent(event))) {
+ && !isTrackpadTwoOrThreeFingerSwipe)) {
if (event.getAction() == MotionEvent.ACTION_UP) {
expand(true /* animate */);
}
@@ -4914,7 +4928,7 @@
break;
case MotionEvent.ACTION_POINTER_UP:
- if (isTrackpadMotionEvent(event)) {
+ if (isTrackpadTwoOrThreeFingerSwipe) {
break;
}
final int upPointer = event.getPointerId(event.getActionIndex());
@@ -4933,7 +4947,7 @@
mShadeLog.logMotionEventStatusBarState(event,
mStatusBarStateController.getState(),
"handleTouch: pointer down action");
- if (!isTrackpadMotionEvent(event)
+ if (!isTrackpadTwoOrThreeFingerSwipe
&& mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
mMotionAborted = true;
endMotionEvent(event, x, y, true /* forceCancel */);
@@ -5008,12 +5022,6 @@
mShadeLog.logHandleTouchLastReturn(event, !mGestureWaitForTouchSlop, mTracking);
return !mGestureWaitForTouchSlop || mTracking;
}
-
- private boolean isTrackpadMotionEvent(MotionEvent ev) {
- return mTrackpadGestureFeaturesEnabled && (
- ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
- || ev.getClassification() == CLASSIFICATION_TWO_FINGER_SWIPE);
- }
}
private final class HeadsUpNotificationViewControllerImpl implements
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 203355e..0548180 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -17,6 +17,7 @@
import android.view.MotionEvent
import android.view.ViewGroup
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -218,6 +219,16 @@
val shadeNotificationPresenter: ShadeNotificationPresenter
companion object {
+ /**
+ * Returns a multiplicative factor to use when determining the falsing threshold for touches
+ * on the shade. The factor will be larger when the device is waking up due to a touch or
+ * gesture.
+ */
+ @JvmStatic
+ fun getFalsingThresholdFactor(wakefulness: WakefulnessModel): Float {
+ return if (wakefulness.isDeviceInteractiveFromTapOrGesture()) 1.5f else 1.0f
+ }
+
const val WAKEUP_ANIMATION_DELAY_MS = 250
const val FLING_MAX_LENGTH_SECONDS = 0.6f
const val FLING_SPEED_UP_FACTOR = 0.6f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 0c4b092..7dbca42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -552,6 +552,9 @@
Runnable r = () -> mMainHandler.post(
() -> openGutsInternal(view, x, y, menuItem));
+ // If the bouncer shows, it will block the TOUCH_UP event from reaching the notif,
+ // so explicitly mark it as unpressed here to reset the touch animation.
+ view.setPressed(false);
mActivityStarter.executeRunnableDismissingKeyguard(
r,
null /* cancelAction */,
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 cdc7cee..c452c0f 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
@@ -104,13 +104,13 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -315,7 +315,7 @@
}
};
private NotificationStackScrollLogger mLogger;
- private CentralSurfaces mCentralSurfaces;
+ private NotificationsController mNotificationsController;
private ActivityStarter mActivityStarter;
private final int[] mTempInt2 = new int[2];
private boolean mGenerateChildOrderChangedEvent;
@@ -3989,7 +3989,7 @@
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
resetScrollPosition();
- mCentralSurfaces.resetUserExpandedStates();
+ mNotificationsController.resetUserExpandedStates();
clearTemporaryViews();
clearUserLockedViews();
cancelActiveSwipe();
@@ -4570,8 +4570,8 @@
return max + getStackTranslation();
}
- public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
- this.mCentralSurfaces = centralSurfaces;
+ public void setNotificationsController(NotificationsController notificationsController) {
+ this.mNotificationsController = notificationsController;
}
public void setActivityStarter(ActivityStarter activityStarter) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7b046d6..12b2ef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -61,6 +61,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -69,6 +70,7 @@
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -98,6 +100,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -144,6 +147,7 @@
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
+ private final NotificationsController mNotificationsController;
private final NotificationVisibilityProvider mVisibilityProvider;
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationRoundnessManager mNotificationRoundnessManager;
@@ -170,6 +174,7 @@
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
+ private final KeyguardInteractor mKeyguardInteractor;
private final NotificationLockscreenUserManager mLockscreenUserManager;
// TODO: CentralSurfaces should be encapsulated behind a Controller
private final CentralSurfaces mCentralSurfaces;
@@ -428,7 +433,7 @@
@Override
public void onSnooze(StatusBarNotification sbn,
NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
- mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
+ mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
}
@Override
@@ -558,7 +563,8 @@
@Override
public float getFalsingThresholdFactor() {
- return mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+ return ShadeViewController.getFalsingThresholdFactor(
+ mKeyguardInteractor.getWakefulnessModel().getValue());
}
@Override
@@ -612,6 +618,7 @@
NotificationStackScrollLayout view,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
NotificationGutsManager notificationGutsManager,
+ NotificationsController notificationsController,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManagerPhone headsUpManager,
NotificationRoundnessManager notificationRoundnessManager,
@@ -622,6 +629,7 @@
SysuiStatusBarStateController statusBarStateController,
KeyguardMediaController keyguardMediaController,
KeyguardBypassController keyguardBypassController,
+ KeyguardInteractor keyguardInteractor,
ZenModeController zenModeController,
NotificationLockscreenUserManager lockscreenUserManager,
Optional<NotificationListViewModel> nsslViewModel,
@@ -659,6 +667,7 @@
mLogger = logger;
mAllowLongPress = allowLongPress;
mNotificationGutsManager = notificationGutsManager;
+ mNotificationsController = notificationsController;
mVisibilityProvider = visibilityProvider;
mHeadsUpManager = headsUpManager;
mNotificationRoundnessManager = notificationRoundnessManager;
@@ -669,6 +678,7 @@
mStatusBarStateController = statusBarStateController;
mKeyguardMediaController = keyguardMediaController;
mKeyguardBypassController = keyguardBypassController;
+ mKeyguardInteractor = keyguardInteractor;
mZenModeController = zenModeController;
mLockscreenUserManager = lockscreenUserManager;
mViewModel = nsslViewModel;
@@ -708,7 +718,7 @@
mView.setController(this);
mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
- mView.setCentralSurfaces(mCentralSurfaces);
+ mView.setNotificationsController(mNotificationsController);
mView.setActivityStarter(mActivityStarter);
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index b956207..7e327e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -34,6 +34,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.SwipeHelper;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -545,14 +546,17 @@
private final FeatureFlags mFeatureFlags;
private NotificationCallback mNotificationCallback;
private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
+ private DumpManager mDumpManager;
private NotificationRoundnessManager mNotificationRoundnessManager;
@Inject
Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
+ DumpManager dumpManager,
FalsingManager falsingManager, FeatureFlags featureFlags,
NotificationRoundnessManager notificationRoundnessManager) {
mResources = resources;
mViewConfiguration = viewConfiguration;
+ mDumpManager = dumpManager;
mFalsingManager = falsingManager;
mFeatureFlags = featureFlags;
mNotificationRoundnessManager = notificationRoundnessManager;
@@ -570,9 +574,12 @@
}
NotificationSwipeHelper build() {
- return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
+ NotificationSwipeHelper swipeHelper = new NotificationSwipeHelper(
+ mResources, mViewConfiguration, mFalsingManager,
mFeatureFlags, mNotificationCallback, mOnMenuEventListener,
mNotificationRoundnessManager);
+ mDumpManager.registerDumpable(swipeHelper);
+ return swipeHelper;
}
}
}
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 0b02f33..8bd9158 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -26,7 +26,6 @@
import android.os.Bundle;
import android.os.PowerManager;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
@@ -44,7 +43,6 @@
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -224,8 +222,6 @@
boolean isLaunchingActivityOverLockscreen();
- boolean isWakeUpComingFromTouch();
-
void onKeyguardViewManagerStatesUpdated();
boolean isPulsing();
@@ -295,8 +291,6 @@
void readyForKeyguardDone();
- void resetUserExpandedStates();
-
void setLockscreenUser(int newUserId);
void showKeyguard();
@@ -385,9 +379,6 @@
boolean isDeviceInteractive();
- void setNotificationSnoozed(StatusBarNotification sbn,
- NotificationSwipeActionHelper.SnoozeOption snoozeOption);
-
void awakenDreams();
void clearNotificationEffects();
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 fde08bd..45b65bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -173,7 +173,6 @@
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
@@ -466,8 +465,6 @@
private final LightRevealScrim mLightRevealScrim;
private PowerButtonReveal mPowerButtonReveal;
- private boolean mWakeUpComingFromTouch;
-
/**
* Whether we should delay the wakeup animation (which shows the notifications and moves the
* clock view). This is typically done when waking up from a 'press to unlock' gesture on a
@@ -1625,7 +1622,6 @@
if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
mPowerManager.wakeUp(
time, wakeReason, "com.android.systemui:" + why);
- mWakeUpComingFromTouch = true;
mFalsingCollector.onScreenOnFromTouch();
}
}
@@ -1802,11 +1798,6 @@
return mIsLaunchingActivityOverLockscreen;
}
- @Override
- public boolean isWakeUpComingFromTouch() {
- return mWakeUpComingFromTouch;
- }
-
/**
* To be called when there's a state change in StatusBarKeyguardViewManager.
*/
@@ -1935,7 +1926,6 @@
SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION,
"com.android.systemui:full_screen_intent");
- mWakeUpComingFromTouch = false;
}
}
@@ -2323,7 +2313,7 @@
mNotificationShadeWindowController.setNotTouchable(false);
}
finishBarAnimations();
- resetUserExpandedStates();
+ mNotificationsController.resetUserExpandedStates();
}
Trace.endSection();
}
@@ -2342,11 +2332,6 @@
}
};
- @Override
- public void resetUserExpandedStates() {
- mNotificationsController.resetUserExpandedStates();
- }
-
/**
* Notify the shade controller that the current user changed
*
@@ -3116,7 +3101,6 @@
releaseGestureWakeLock();
mLaunchCameraWhenFinishedWaking = false;
mDeviceInteractive = false;
- mWakeUpComingFromTouch = false;
updateVisibleToUser();
updateNotificationPanelTouchState();
@@ -3587,12 +3571,6 @@
};
@Override
- public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
- mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
- }
-
-
- @Override
public void awakenDreams() {
mUiBgExecutor.execute(() -> {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index b36ba384..d42e30c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -110,6 +110,9 @@
/** See [MobileIconsInteractor.isForceHidden]. */
val isForceHidden: Flow<Boolean>
+
+ /** True when in carrier network change mode */
+ val carrierNetworkChangeActive: StateFlow<Boolean>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
@@ -135,6 +138,9 @@
override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
+ override val carrierNetworkChangeActive: StateFlow<Boolean> =
+ connectionRepository.carrierNetworkChangeActive
+
// True if there exists _any_ icon override for this carrierId. Note that overrides can include
// any or none of the icon groups defined in MobileMappings, so we still need to check on a
// per-network-type basis whether or not the given icon group is overridden
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 5b7d45b..a2a247a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -109,12 +109,7 @@
viewModel.subscriptionId,
icon,
)
- mobileDrawable.level =
- SignalDrawable.getState(
- icon.level,
- icon.numberOfLevels,
- icon.showExclamationMark,
- )
+ mobileDrawable.level = icon.toSignalDrawableState()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
index be2e41a..6de3f85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.model
+import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
@@ -24,6 +25,7 @@
val level: Int,
val numberOfLevels: Int,
val showExclamationMark: Boolean,
+ val carrierNetworkChange: Boolean,
) : Diffable<SignalIconModel> {
// TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
@@ -36,17 +38,30 @@
if (prevVal.showExclamationMark != showExclamationMark) {
row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
}
+ if (prevVal.carrierNetworkChange != carrierNetworkChange) {
+ row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
+ }
}
override fun logFull(row: TableRowLogger) {
row.logChange(COL_LEVEL, level)
row.logChange(COL_NUM_LEVELS, numberOfLevels)
row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+ row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange)
}
+ /** Convert this model to an [Int] consumable by [SignalDrawable]. */
+ fun toSignalDrawableState(): Int =
+ if (carrierNetworkChange) {
+ SignalDrawable.getCarrierChangeState(numberOfLevels)
+ } else {
+ SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+ }
+
companion object {
private const val COL_LEVEL = "level"
private const val COL_NUM_LEVELS = "numLevels"
private const val COL_SHOW_EXCLAMATION = "showExclamation"
+ private const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChange"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 54730ed..35f4f9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -122,13 +122,20 @@
level = shownLevel.value,
numberOfLevels = iconInteractor.numberOfLevels.value,
showExclamationMark = showExclamationMark.value,
+ carrierNetworkChange = iconInteractor.carrierNetworkChangeActive.value,
)
combine(
shownLevel,
iconInteractor.numberOfLevels,
showExclamationMark,
- ) { shownLevel, numberOfLevels, showExclamationMark ->
- SignalIconModel(shownLevel, numberOfLevels, showExclamationMark)
+ iconInteractor.carrierNetworkChangeActive,
+ ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange ->
+ SignalIconModel(
+ shownLevel,
+ numberOfLevels,
+ showExclamationMark,
+ carrierNetworkChange,
+ )
}
.distinctUntilChanged()
.logDiffsForTable(
@@ -152,8 +159,10 @@
iconInteractor.isDataEnabled,
iconInteractor.alwaysShowDataRatIcon,
iconInteractor.mobileIsDefault,
- ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault ->
- alwaysShow || (dataEnabled && dataConnected && mobileIsDefault)
+ iconInteractor.carrierNetworkChangeActive,
+ ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault, carrierNetworkChange ->
+ alwaysShow ||
+ (!carrierNetworkChange && (dataEnabled && dataConnected && mobileIsDefault))
}
.distinctUntilChanged()
.logDiffsForTable(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 565fc57..f6450a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -253,11 +253,11 @@
getViewConstraint(mSecurityViewFlipper.getId());
ConstraintSet.Constraint userSwitcherConstraint =
getViewConstraint(R.id.keyguard_bouncer_user_switcher);
- assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
- assertThat(viewFlipperConstraint.layout.leftToRight).isEqualTo(
+ assertThat(viewFlipperConstraint.layout.endToEnd).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.startToEnd).isEqualTo(
R.id.keyguard_bouncer_user_switcher);
- assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
- assertThat(userSwitcherConstraint.layout.rightToLeft).isEqualTo(
+ assertThat(userSwitcherConstraint.layout.startToStart).isEqualTo(PARENT_ID);
+ assertThat(userSwitcherConstraint.layout.endToStart).isEqualTo(
mSecurityViewFlipper.getId());
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
@@ -377,7 +377,7 @@
ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(
mSecurityViewFlipper.getId());
- assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
+ assertThat(viewFlipperConstraint.layout.startToStart).isEqualTo(PARENT_ID);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index df08dec..b3f9958 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility;
+import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
@@ -30,6 +31,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,6 +55,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -62,6 +65,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -87,6 +91,11 @@
private WindowMagnificationSettings mWindowMagnificationSettings;
private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+ private ArgumentCaptor<Float> mSecureSettingsScaleCaptor;
+ private ArgumentCaptor<String> mSecureSettingsNameCaptor;
+ private ArgumentCaptor<Integer> mSecureSettingsUserHandleCaptor;
+ private ArgumentCaptor<Float> mCallbackMagnifierScaleCaptor;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -102,6 +111,10 @@
mSecureSettings);
mSettingView = mWindowMagnificationSettings.getSettingView();
+ mSecureSettingsScaleCaptor = ArgumentCaptor.forClass(Float.class);
+ mSecureSettingsNameCaptor = ArgumentCaptor.forClass(String.class);
+ mSecureSettingsUserHandleCaptor = ArgumentCaptor.forClass(Integer.class);
+ mCallbackMagnifierScaleCaptor = ArgumentCaptor.forClass(Float.class);
}
@After
@@ -324,6 +337,20 @@
}
@Test
+ public void showSettingsPanel_observerForMagnificationScaleRegistered() {
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mWindowMagnificationSettings.showSettingPanel();
+
+ verify(mSecureSettings).registerContentObserverForUser(
+ eq(ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE),
+ any(ContentObserver.class),
+ eq(UserHandle.USER_CURRENT));
+ }
+
+ @Test
public void hideSettingsPanel_observerUnregistered() {
setupMagnificationCapabilityAndMode(
/* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
@@ -332,7 +359,162 @@
mWindowMagnificationSettings.showSettingPanel();
mWindowMagnificationSettings.hideSettingPanel();
- verify(mSecureSettings).unregisterContentObserver(any(ContentObserver.class));
+ verify(mSecureSettings, times(2)).unregisterContentObserver(any(ContentObserver.class));
+ }
+
+ @Test
+ public void seekbarProgress_justInflated_maxValueAndProgressSetCorrectly() {
+ setupScaleInSecureSettings(0f);
+ assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(0);
+ assertThat(mWindowMagnificationSettings.mZoomSeekbar.getMax()).isEqualTo(70);
+ }
+
+ @Test
+ public void seekbarProgress_minMagnification_seekbarProgressIsCorrect() {
+ setupScaleInSecureSettings(0f);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // Seekbar index from 0 to 70. 1.0f scale (A11Y_SCALE_MIN_VALUE) would correspond to 0.
+ assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(0);
+ }
+
+ @Test
+ public void seekbarProgress_belowMinMagnification_seekbarProgressIsZero() {
+ setupScaleInSecureSettings(0f);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mWindowMagnificationSettings.showSettingPanel();
+
+ assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(0);
+ }
+
+ @Test
+ public void seekbarProgress_magnificationBefore_seekbarProgressIsHalf() {
+ setupScaleInSecureSettings(4f);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // float scale : from 1.0f to 8.0f, seekbar index from 0 to 70.
+ // 4.0f would correspond to 30.
+ assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(30);
+ }
+
+ @Test
+ public void seekbarProgress_maxMagnificationBefore_seekbarProgressIsMax() {
+ setupScaleInSecureSettings(8f);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // 8.0f is max magnification {@link MagnificationScaleProvider#MAX_SCALE}.
+ // Max zoom seek bar is 70.
+ assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(70);
+ }
+
+ @Test
+ public void seekbarProgress_aboveMaxMagnificationBefore_seekbarProgressIsMax() {
+ setupScaleInSecureSettings(9f);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // Max zoom seek bar is 70.
+ assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(70);
+ }
+
+ @Test
+ public void seekbarProgress_progressChangedRoughlyHalf_scaleAndCallbackUpdated() {
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
+
+ mWindowMagnificationSettings.mZoomSeekbar.setProgress(30);
+
+ verifyScaleUpdatedInSecureSettings(4f);
+ verifyCallbackOnMagnifierScale(4f);
+ }
+
+ @Test
+ public void seekbarProgress_minProgress_callbackUpdated() {
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
+ // Set progress to non-zero first so onProgressChanged can be triggered upon setting to 0.
+ mWindowMagnificationSettings.mZoomSeekbar.setProgress(30);
+
+ mWindowMagnificationSettings.mZoomSeekbar.setProgress(0);
+
+ // For now, secure settings will not be updated for values < 1.3f. Follow up on this later.
+ verify(mWindowMagnificationSettingsCallback, times(2))
+ .onMagnifierScale(mCallbackMagnifierScaleCaptor.capture());
+ var capturedArgs = mCallbackMagnifierScaleCaptor.getAllValues();
+ assertThat(capturedArgs).hasSize(2);
+ assertThat(capturedArgs.get(1)).isWithin(0.01f).of(1f);
+ }
+
+ @Test
+ public void seekbarProgress_maxProgress_scaleAndCallbackUpdated() {
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
+
+ mWindowMagnificationSettings.mZoomSeekbar.setProgress(70);
+
+ verifyScaleUpdatedInSecureSettings(8f);
+ verifyCallbackOnMagnifierScale(8f);
+ }
+
+ @Test
+ public void seekbarProgress_scaleUpdatedAfterSettingPanelOpened_progressAlsoUpdated() {
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ var contentObserverCaptor = ArgumentCaptor.forClass(ContentObserver.class);
+ mWindowMagnificationSettings.showSettingPanel();
+ verify(mSecureSettings).registerContentObserverForUser(
+ eq(ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE),
+ contentObserverCaptor.capture(),
+ eq(UserHandle.USER_CURRENT));
+
+ // Simulate outside changes.
+ setupScaleInSecureSettings(4f);
+ // Simulate callback due to outside change.
+ contentObserverCaptor.getValue().onChange(/* selfChange= */ false);
+
+ assertThat(mWindowMagnificationSettings.mZoomSeekbar.getProgress()).isEqualTo(30);
+ }
+
+ private void verifyScaleUpdatedInSecureSettings(float scale) {
+ verify(mSecureSettings).putFloatForUser(
+ mSecureSettingsNameCaptor.capture(),
+ mSecureSettingsScaleCaptor.capture(),
+ mSecureSettingsUserHandleCaptor.capture());
+ assertThat(mSecureSettingsScaleCaptor.getValue()).isWithin(0.01f).of(scale);
+ assertThat(mSecureSettingsNameCaptor.getValue())
+ .isEqualTo(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE);
+ assertThat(mSecureSettingsUserHandleCaptor.getValue()).isEqualTo(UserHandle.USER_CURRENT);
+ }
+
+ private void verifyCallbackOnMagnifierScale(float scale) {
+ verify(mWindowMagnificationSettingsCallback)
+ .onMagnifierScale(mCallbackMagnifierScaleCaptor.capture());
+ assertThat(mCallbackMagnifierScaleCaptor.getValue()).isWithin(0.01f).of(scale);
}
private <T extends View> T getInternalView(@IdRes int idRes) {
@@ -351,4 +533,11 @@
ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
UserHandle.USER_CURRENT)).thenReturn(mode);
}
+
+ private void setupScaleInSecureSettings(float scale) {
+ when(mSecureSettings.getFloatForUser(
+ ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ MagnificationConstants.SCALE_MIN_VALUE,
+ UserHandle.USER_CURRENT)).thenReturn(scale);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 40d9009..2908e75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -64,7 +64,6 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -111,7 +110,6 @@
@Mock lateinit var activityTaskManager: ActivityTaskManager
@Mock lateinit var sideFpsView: View
@Mock lateinit var displayManager: DisplayManager
- @Mock lateinit var overviewProxyService: OverviewProxyService
@Mock lateinit var handler: Handler
@Mock lateinit var dumpManager: DumpManager
@Captor lateinit var overlayCaptor: ArgumentCaptor<View>
@@ -262,7 +260,6 @@
fingerprintManager,
windowManager,
activityTaskManager,
- overviewProxyService,
displayManager,
displayStateInteractor,
executor,
@@ -686,18 +683,6 @@
verify(windowManager).removeView(any())
}
- private fun hidesWithTaskbar(visible: Boolean) {
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(true, false)
- executor.runAllReady()
-
- verify(windowManager).addView(any(), any())
- verify(windowManager, never()).removeView(any())
- verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE
- }
-
/**
* {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
* and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index ca6282c..461ec65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -28,6 +28,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamLogger;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
@@ -56,6 +57,8 @@
private FakeFeatureFlags mFeatureFlags;
@Mock
private Observer mObserver;
+ @Mock
+ private DreamLogger mLogger;
@Before
public void setUp() {
@@ -66,7 +69,8 @@
mStateController = new DreamOverlayStateController(
mExecutor,
/* overlayEnabled= */ true,
- mFeatureFlags);
+ mFeatureFlags,
+ mLogger);
mLiveData = new ComplicationCollectionLiveData(mStateController);
}
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 7b41605..2c1ebe4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -58,6 +58,9 @@
@Mock
private FeatureFlags mFeatureFlags;
+ @Mock
+ private DreamLogger mLogger;
+
final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
@@ -405,6 +408,6 @@
}
private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
- return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
+ return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags, mLogger);
}
}
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 d16b757..5dc0e55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -113,6 +113,8 @@
DreamOverlayStateController mDreamOverlayStateController;
@Mock
UserTracker mUserTracker;
+ @Mock
+ DreamLogger mLogger;
@Captor
private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
@@ -146,7 +148,8 @@
mStatusBarWindowStateController,
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
- mUserTracker);
+ mUserTracker,
+ mLogger);
}
@Test
@@ -289,7 +292,8 @@
mStatusBarWindowStateController,
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
- mUserTracker);
+ mUserTracker,
+ mLogger);
controller.onViewAttached();
verify(mView, never()).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
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 4b797cb..953d618 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
@@ -97,7 +97,8 @@
dozeParameters,
authController,
dreamOverlayCallbackController,
- mainDispatcher
+ mainDispatcher,
+ testScope.backgroundScope,
)
}
@@ -343,8 +344,6 @@
)
job.cancel()
- runCurrent()
- verify(wakefulnessLifecycle).removeObserver(captor.value)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 97b18e2..d6ceea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -234,4 +234,17 @@
assertThat(seekBarView.progress).isEqualTo(4000)
verify(mockSeekbarAnimator).start()
}
+
+ @Test
+ fun seekbarActive_animationsDisabled() {
+ // WHEN playing, but animations have been disabled
+ observer.animationEnabled = false
+ val isPlaying = true
+ val isScrubbing = false
+ val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+ observer.onChanged(data)
+
+ // THEN progress drawable does not animate
+ verify(mockSquigglyProgress).animate = false
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index f030a03..7b673bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -25,6 +25,7 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
+import android.database.ContentObserver
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
@@ -40,6 +41,7 @@
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Bundle
+import android.provider.Settings
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -73,6 +75,7 @@
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.player.MediaDeviceData
import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.models.player.SeekBarObserver
import com.android.systemui.media.controls.models.player.SeekBarViewModel
import com.android.systemui.media.controls.models.recommendation.KEY_SMARTSPACE_APP_NAME
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
@@ -97,6 +100,7 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
@@ -109,6 +113,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock
@@ -233,6 +238,8 @@
this.set(Flags.MEDIA_EXPLICIT_INDICATOR, true)
this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
}
+ @Mock private lateinit var globalSettings: GlobalSettings
+ @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -273,7 +280,8 @@
activityIntentHelper,
lockscreenUserManager,
broadcastDialogController,
- fakeFeatureFlag
+ fakeFeatureFlag,
+ globalSettings,
) {
override fun loadAnimator(
animId: Int,
@@ -284,6 +292,12 @@
}
}
+ verify(globalSettings)
+ .registerContentObserver(
+ eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
+ settingsObserverCaptor.capture()
+ )
+
initGutsViewHolderMocks()
initMediaViewHolderMocks()
@@ -955,6 +969,30 @@
}
@Test
+ fun animationSettingChange_updateSeekbar() {
+ // When animations are enabled
+ globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+ val progress = 0.5
+ val state = mediaData.copy(resumption = true, resumeProgress = progress)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+
+ val captor = argumentCaptor<SeekBarObserver>()
+ verify(seekBarData).observeForever(captor.capture())
+ val seekBarObserver = captor.value!!
+
+ // Then the seekbar is set to animate
+ assertThat(seekBarObserver.animationEnabled).isTrue()
+
+ // When the setting changes,
+ globalSettings.putFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 0f)
+ settingsObserverCaptor.value!!.onChange(false)
+
+ // Then the seekbar is set to not animate
+ assertThat(seekBarObserver.animationEnabled).isFalse()
+ }
+
+ @Test
fun bindNotificationActions() {
val icon = context.getDrawable(android.R.drawable.ic_media_play)
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 41351e5..ee382d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -315,6 +315,7 @@
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
+ protected FakeKeyguardRepository mFakeKeyguardRepository;
protected KeyguardInteractor mKeyguardInteractor;
protected NotificationPanelViewController.TouchHandler mTouchHandler;
protected ConfigurationController mConfigurationController;
@@ -342,10 +343,12 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mMainDispatcher = getMainDispatcher();
- mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(
- new FakeKeyguardRepository());
+ KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
+ KeyguardInteractorFactory.create();
+ mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
+ mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
+ mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
- mKeyguardInteractor = KeyguardInteractorFactory.create().getKeyguardInteractor();
SystemClock systemClock = new FakeSystemClock();
mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
mInteractionJankMonitor, mShadeExpansionStateManager);
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 a5a9de5..b1f8475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -58,6 +58,9 @@
import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.systemui.DejankUtils;
import com.android.systemui.R;
+import com.android.systemui.keyguard.shared.model.WakeSleepReason;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
+import com.android.systemui.keyguard.shared.model.WakefulnessState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
@@ -1162,4 +1165,43 @@
when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
}
+
+ @Test
+ public void getFalsingThreshold_deviceNotInteractive_isQsThreshold() {
+ mFakeKeyguardRepository.setWakefulnessModel(
+ new WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ /* lastWakeReason= */ WakeSleepReason.TAP,
+ /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+ );
+ when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+ assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
+ }
+
+ @Test
+ public void getFalsingThreshold_lastWakeNotDueToTouch_isQsThreshold() {
+ mFakeKeyguardRepository.setWakefulnessModel(
+ new WakefulnessModel(
+ WakefulnessState.AWAKE,
+ /* lastWakeReason= */ WakeSleepReason.POWER_BUTTON,
+ /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+ );
+ when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+ assertThat(mNotificationPanelViewController.getFalsingThreshold()).isEqualTo(14);
+ }
+
+ @Test
+ public void getFalsingThreshold_lastWakeDueToTouch_greaterThanQsThreshold() {
+ mFakeKeyguardRepository.setWakefulnessModel(
+ new WakefulnessModel(
+ WakefulnessState.AWAKE,
+ /* lastWakeReason= */ WakeSleepReason.TAP,
+ /* lastSleepReason= */ WakeSleepReason.POWER_BUTTON)
+ );
+ when(mQsController.getFalsingThreshold()).thenReturn(14);
+
+ assertThat(mNotificationPanelViewController.getFalsingThreshold()).isGreaterThan(14);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6a0e3c6..10aac4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -47,6 +47,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -70,6 +71,7 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
@@ -105,6 +107,7 @@
public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationGutsManager mNotificationGutsManager;
+ @Mock private NotificationsController mNotificationsController;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@@ -117,6 +120,7 @@
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
@Mock private KeyguardBypassController mKeyguardBypassController;
+ @Mock private KeyguardInteractor mKeyguardInteractor;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private MetricsLogger mMetricsLogger;
@Mock private DumpManager mDumpManager;
@@ -453,6 +457,7 @@
mNotificationStackScrollLayout,
true,
mNotificationGutsManager,
+ mNotificationsController,
mVisibilityProvider,
mHeadsUpManager,
mNotificationRoundnessManager,
@@ -463,6 +468,7 @@
mSysuiStatusBarStateController,
mKeyguardMediaController,
mKeyguardBypassController,
+ mKeyguardInteractor,
mZenModeController,
mNotificationLockscreenUserManager,
Optional.<NotificationListViewModel>empty(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a4ee349..c0d98b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -79,9 +79,9 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -114,7 +114,7 @@
private TestableResources mTestableResources;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
- @Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private NotificationsController mNotificationsController;
@Mock private SysuiStatusBarStateController mBarState;
@Mock private GroupMembershipManager mGroupMembershipManger;
@Mock private GroupExpansionManager mGroupExpansionManager;
@@ -181,7 +181,7 @@
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
mStackScroller.setShelfController(notificationShelfController);
- mStackScroller.setCentralSurfaces(mCentralSurfaces);
+ mStackScroller.setNotificationsController(mNotificationsController);
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index f054422e..c4e4193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -40,6 +40,8 @@
)
)
+ override val carrierNetworkChangeActive = MutableStateFlow(false)
+
override val mobileIsDefault = MutableStateFlow(true)
override val networkTypeIconGroup =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
new file mode 100644
index 0000000..01c388a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.model
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+internal class SignalIconModelParameterizedTest(private val testCase: TestCase) : SysuiTestCase() {
+ @Test
+ fun drawableFromModel_level0_numLevels4_noExclamation_notCarrierNetworkChange() {
+ val model =
+ SignalIconModel(
+ level = 0,
+ numberOfLevels = 4,
+ showExclamationMark = false,
+ carrierNetworkChange = false
+ )
+
+ val expected =
+ SignalDrawable.getState(/* level = */ 0, /* numLevels = */ 4, /* cutOut = */ false)
+
+ assertThat(model.toSignalDrawableState()).isEqualTo(expected)
+ }
+
+ @Test
+ fun runTest() {
+ val model = testCase.toSignalIconModel()
+ assertThat(model.toSignalDrawableState()).isEqualTo(testCase.expected)
+ }
+
+ internal data class TestCase(
+ val level: Int,
+ val numberOfLevels: Int,
+ val showExclamation: Boolean,
+ val carrierNetworkChange: Boolean,
+ val expected: Int,
+ ) {
+ fun toSignalIconModel() =
+ SignalIconModel(
+ level = level,
+ numberOfLevels = numberOfLevels,
+ showExclamationMark = showExclamation,
+ carrierNetworkChange = carrierNetworkChange,
+ )
+
+ override fun toString(): String =
+ "INPUT(level=$level," +
+ "numberOfLevels=$numberOfLevels," +
+ "showExclamation=$showExclamation," +
+ "carrierNetworkChange=$carrierNetworkChange)"
+ }
+
+ companion object {
+ @Parameters(name = "{0}") @JvmStatic fun data() = testData()
+
+ private fun testData(): Collection<TestCase> =
+ listOf(
+ TestCase(
+ level = 0,
+ numberOfLevels = 4,
+ showExclamation = false,
+ carrierNetworkChange = false,
+ expected = SignalDrawable.getState(0, 4, false)
+ ),
+ TestCase(
+ level = 0,
+ numberOfLevels = 4,
+ showExclamation = false,
+ carrierNetworkChange = true,
+ expected = SignalDrawable.getCarrierChangeState(4)
+ ),
+ TestCase(
+ level = 2,
+ numberOfLevels = 5,
+ showExclamation = false,
+ carrierNetworkChange = false,
+ expected = SignalDrawable.getState(2, 5, false)
+ ),
+ TestCase(
+ level = 2,
+ numberOfLevels = 5,
+ showExclamation = true,
+ carrierNetworkChange = false,
+ expected = SignalDrawable.getState(2, 5, true)
+ ),
+ TestCase(
+ level = 2,
+ numberOfLevels = 5,
+ showExclamation = true,
+ carrierNetworkChange = true,
+ expected = SignalDrawable.getCarrierChangeState(5)
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 2b7bc78..b5ab29d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -273,6 +273,27 @@
}
@Test
+ fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+ interactor.carrierNetworkChangeActive.value = true
+ interactor.level.value = 1
+
+ assertThat(latest!!.level).isEqualTo(1)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ // SignalIconModel respects the current level
+ interactor.level.value = 2
+
+ assertThat(latest!!.level).isEqualTo(2)
+ assertThat(latest!!.carrierNetworkChange).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun contentDescription_notInService_usesNoPhone() =
testScope.runTest {
var latest: ContentDescription? = null
@@ -338,6 +359,20 @@
}
@Test
+ fun networkType_null_whenCarrierNetworkChangeActive() =
+ testScope.runTest {
+ interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G)
+ interactor.carrierNetworkChangeActive.value = true
+ interactor.mobileIsDefault.value = true
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
fun networkTypeIcon_notNull_whenEnabled() =
testScope.runTest {
val expected =
@@ -617,13 +652,14 @@
}
private fun createAndSetViewModel() {
- underTest = MobileIconViewModel(
- SUB_1_ID,
- interactor,
- airplaneModeInteractor,
- constants,
- testScope.backgroundScope,
- )
+ underTest =
+ MobileIconViewModel(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
}
companion object {
@@ -632,10 +668,20 @@
/** Convenience constructor for these tests */
fun defaultSignal(level: Int = 1): SignalIconModel {
- return SignalIconModel(level, NUM_LEVELS, showExclamationMark = false)
+ return SignalIconModel(
+ level,
+ NUM_LEVELS,
+ showExclamationMark = false,
+ carrierNetworkChange = false,
+ )
}
fun emptySignal(): SignalIconModel =
- SignalIconModel(level = 0, numberOfLevels = NUM_LEVELS, showExclamationMark = true)
+ SignalIconModel(
+ level = 0,
+ numberOfLevels = NUM_LEVELS,
+ showExclamationMark = true,
+ carrierNetworkChange = false,
+ )
}
}
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 b52a768..f6cbb07 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
@@ -81,7 +81,7 @@
MutableStateFlow(
WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
)
- override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
+ override val wakefulness = _wakefulnessModel
private val _isUdfpsSupported = MutableStateFlow(false)
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ad038d1..fdb28ba 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4006,17 +4006,14 @@
@Override
public boolean registerProxyForDisplay(IAccessibilityServiceClient client, int displayId)
throws RemoteException {
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ mSecurityPolicy.checkForAccessibilityPermissionOrRole();
if (client == null) {
return false;
}
if (displayId < 0) {
throw new IllegalArgumentException("The display id " + displayId + " is invalid.");
}
- if (displayId == Display.DEFAULT_DISPLAY) {
- throw new IllegalArgumentException("The default display cannot be proxy-ed.");
- }
if (!isTrackedDisplay(displayId)) {
throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
+ " not tracked by accessibility.");
@@ -4025,6 +4022,10 @@
throw new IllegalArgumentException("The display " + displayId + " is already being"
+ " proxy-ed");
}
+ if (!mProxyManager.displayBelongsToCaller(Binder.getCallingUid(), displayId)) {
+ throw new SecurityException("The display " + displayId + " does not belong to"
+ + " the caller.");
+ }
final long identity = Binder.clearCallingIdentity();
try {
@@ -4042,8 +4043,8 @@
@Override
public boolean unregisterProxyForDisplay(int displayId) {
- mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.CREATE_VIRTUAL_DEVICE);
+ mSecurityPolicy.checkForAccessibilityPermissionOrRole();
final long identity = Binder.clearCallingIdentity();
try {
return mProxyManager.unregisterProxy(displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 9335626..f45fa92 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -18,6 +18,7 @@
import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_BY_ADMIN;
import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -25,6 +26,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.app.role.RoleManager;
import android.appwidget.AppWidgetManagerInternal;
import android.content.ComponentName;
import android.content.Context;
@@ -675,6 +677,42 @@
}
/**
+ * Throws a SecurityException if the caller has neither the MANAGE_ACCESSIBILITY permission nor
+ * the COMPANION_DEVICE_APP_STREAMING role.
+ */
+ public void checkForAccessibilityPermissionOrRole() {
+ final boolean canManageAccessibility =
+ mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ == PackageManager.PERMISSION_GRANTED;
+ if (canManageAccessibility) {
+ return;
+ }
+ final int callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+ if (roleManager != null) {
+ final List<String> holders = roleManager.getRoleHoldersAsUser(
+ DEVICE_PROFILE_APP_STREAMING, UserHandle.getUserHandleForUid(callingUid));
+ final String[] packageNames = mPackageManager.getPackagesForUid(callingUid);
+ if (packageNames != null) {
+ for (String packageName : packageNames) {
+ if (holders.contains(packageName)) {
+ return;
+ }
+ }
+ }
+ }
+ throw new SecurityException(
+ "Cannot register a proxy for a device without the "
+ + "android.app.role.COMPANION_DEVICE_APP_STREAMING role or the"
+ + " MANAGE_ACCESSIBILITY permission.");
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Called after a service was bound or unbound. Checks the current bound accessibility
* services and updates alarms.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 6dc8fb3..70882c6 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -24,6 +24,7 @@
import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
+import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.content.ComponentName;
import android.content.Context;
@@ -319,6 +320,25 @@
return isTrackingDeviceId;
}
+ /** Returns true if the display belongs to one of the caller's virtual devices. */
+ public boolean displayBelongsToCaller(int callingUid, int proxyDisplayId) {
+ final VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
+ final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+ if (vdm == null || localVdm == null) {
+ return false;
+ }
+ final List<VirtualDevice> virtualDevices = vdm.getVirtualDevices();
+ for (VirtualDevice device : virtualDevices) {
+ if (localVdm.getDisplayIdsForDevice(device.getDeviceId()).contains(proxyDisplayId)) {
+ final int ownerUid = localVdm.getDeviceOwnerUid(device.getDeviceId());
+ if (callingUid == ownerUid) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Sends AccessibilityEvents to a proxy given the event's displayId.
*/
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
index 8e1aa38..41c3dcb 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationScaleProvider.java
@@ -16,6 +16,9 @@
package com.android.server.accessibility.magnification;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
@@ -37,8 +40,9 @@
@VisibleForTesting
protected static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
- public static final float MIN_SCALE = 1.0f;
- public static final float MAX_SCALE = 8.0f;
+
+ public static final float MIN_SCALE = SCALE_MIN_VALUE;
+ public static final float MAX_SCALE = SCALE_MAX_VALUE;
private final Context mContext;
// Stores the scale for non-default displays.
@@ -134,6 +138,6 @@
}
static float constrainScale(float scale) {
- return MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
+ return MathUtils.constrain(scale, SCALE_MIN_VALUE, SCALE_MAX_VALUE);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 4576abb..563e431 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4857,16 +4857,16 @@
return null;
}
- final boolean isWhitelisted = mService
+ final boolean isAllowlisted = mService
.isWhitelistedForAugmentedAutofillLocked(mComponentName);
- if (!isWhitelisted) {
+ if (!isAllowlisted) {
if (sVerbose) {
Slog.v(TAG, "triggerAugmentedAutofillLocked(): "
+ ComponentName.flattenToShortString(mComponentName) + " not whitelisted ");
}
logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
- mCurrentViewId, isWhitelisted, /* isInline= */ null);
+ mCurrentViewId, isAllowlisted, /* isInline= */ null);
return null;
}
@@ -4899,32 +4899,9 @@
final AutofillId focusedId = mCurrentViewId;
- final Function<InlineFillUi, Boolean> inlineSuggestionsResponseCallback =
- response -> {
- synchronized (mLock) {
- return mInlineSessionController.setInlineFillUiLocked(response);
- }
- };
final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill =
- (inlineSuggestionsRequest) -> {
- synchronized (mLock) {
- logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
- focusedId, isWhitelisted, inlineSuggestionsRequest != null);
- remoteService.onRequestAutofillLocked(id, mClient,
- taskId, mComponentName, mActivityToken,
- AutofillId.withoutSession(focusedId), currentValue,
- inlineSuggestionsRequest, inlineSuggestionsResponseCallback,
- /*onErrorCallback=*/ () -> {
- synchronized (mLock) {
- cancelAugmentedAutofillLocked();
-
- // Also cancel augmented in IME
- mInlineSessionController.setInlineFillUiLocked(
- InlineFillUi.emptyUi(mCurrentViewId));
- }
- }, mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
- }
- };
+ new AugmentedAutofillInlineSuggestionRequestConsumer(
+ this, focusedId, isAllowlisted, mode, currentValue);
// When the inline suggestion render service is available and the view is focused, there
// are 3 cases when augmented autofill should ask IME for inline suggestion request,
@@ -4942,14 +4919,11 @@
|| mSessionFlags.mExpiredResponse)
&& (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
- remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
- (extras) -> {
- synchronized (mLock) {
- mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
- focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
- extras);
- }
- }, mHandler));
+ remoteRenderService.getInlineSuggestionsRendererInfo(
+ new RemoteCallback(
+ new AugmentedAutofillInlineSuggestionRendererOnResultListener(
+ this, focusedId, requestAugmentedAutofill),
+ mHandler));
} else {
requestAugmentedAutofill.accept(
mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
@@ -4960,6 +4934,169 @@
return mAugmentedAutofillDestroyer;
}
+ private static class AugmentedAutofillInlineSuggestionRendererOnResultListener
+ implements RemoteCallback.OnResultListener {
+
+ WeakReference<Session> mSessionWeakRef;
+ final AutofillId mFocusedId;
+ Consumer<InlineSuggestionsRequest> mRequestAugmentedAutofill;
+
+ AugmentedAutofillInlineSuggestionRendererOnResultListener(
+ Session session,
+ AutofillId focussedId,
+ Consumer<InlineSuggestionsRequest> requestAugmentedAutofill) {
+ mSessionWeakRef = new WeakReference<>(session);
+ mFocusedId = focussedId;
+ mRequestAugmentedAutofill = requestAugmentedAutofill;
+ }
+
+ @Override
+ public void onResult(@Nullable Bundle result) {
+ Session session = mSessionWeakRef.get();
+
+ if (logIfSessionNull(
+ session, "AugmentedAutofillInlineSuggestionRendererOnResultListener:")) {
+ return;
+ }
+ synchronized (session.mLock) {
+ session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+ mFocusedId, /*requestConsumer=*/ mRequestAugmentedAutofill,
+ result);
+ }
+ }
+ }
+
+ private static class AugmentedAutofillInlineSuggestionRequestConsumer
+ implements Consumer<InlineSuggestionsRequest> {
+
+ WeakReference<Session> mSessionWeakRef;
+ final AutofillId mFocusedId;
+ final boolean mIsAllowlisted;
+ final int mMode;
+ final AutofillValue mCurrentValue;
+
+ AugmentedAutofillInlineSuggestionRequestConsumer(
+ Session session,
+ AutofillId focussedId,
+ boolean isAllowlisted,
+ int mode,
+ AutofillValue currentValue) {
+ mSessionWeakRef = new WeakReference<>(session);
+ mFocusedId = focussedId;
+ mIsAllowlisted = isAllowlisted;
+ mMode = mode;
+ mCurrentValue = currentValue;
+
+ }
+ @Override
+ public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) {
+ Session session = mSessionWeakRef.get();
+
+ if (logIfSessionNull(
+ session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) {
+ return;
+ }
+ session.onAugmentedAutofillInlineSuggestionAccept(
+ inlineSuggestionsRequest, mFocusedId, mIsAllowlisted, mMode, mCurrentValue);
+
+ }
+ }
+
+ private static class AugmentedAutofillInlineSuggestionsResponseCallback
+ implements Function<InlineFillUi, Boolean> {
+
+ WeakReference<Session> mSessionWeakRef;
+
+ AugmentedAutofillInlineSuggestionsResponseCallback(Session session) {
+ this.mSessionWeakRef = new WeakReference<>(session);
+ }
+
+ @Override
+ public Boolean apply(InlineFillUi inlineFillUi) {
+ Session session = mSessionWeakRef.get();
+
+ if (logIfSessionNull(
+ session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) {
+ return false;
+ }
+
+ synchronized (session.mLock) {
+ return session.mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
+ }
+ }
+ }
+
+ private static class AugmentedAutofillErrorCallback implements Runnable {
+
+ WeakReference<Session> mSessionWeakRef;
+
+ AugmentedAutofillErrorCallback(Session session) {
+ this.mSessionWeakRef = new WeakReference<>(session);
+ }
+
+ @Override
+ public void run() {
+ Session session = mSessionWeakRef.get();
+
+ if (logIfSessionNull(session, "AugmentedAutofillErrorCallback:")) {
+ return;
+ }
+ session.onAugmentedAutofillErrorCallback();
+ }
+ }
+
+ /**
+ * If the session is null or has been destroyed, log the error msg, and return true.
+ * This is a helper function intended to be called when de-referencing from a weak reference.
+ * @param session
+ * @param logPrefix
+ * @return true if the session is null, false otherwise.
+ */
+ private static boolean logIfSessionNull(Session session, String logPrefix) {
+ if (session == null) {
+ Slog.wtf(TAG, logPrefix + " Session null");
+ return true;
+ }
+ if (session.mDestroyed) {
+ // TODO: Update this to return in this block. We aren't doing this to preserve the
+ // behavior, but can be modified once we have more time to soak the changes.
+ Slog.w(TAG, logPrefix + " Session destroyed, but following through");
+ // Follow-through
+ }
+ return false;
+ }
+
+ private void onAugmentedAutofillInlineSuggestionAccept(
+ InlineSuggestionsRequest inlineSuggestionsRequest,
+ AutofillId focussedId,
+ boolean isAllowlisted,
+ int mode,
+ AutofillValue currentValue) {
+ synchronized (mLock) {
+ final RemoteAugmentedAutofillService remoteService =
+ mService.getRemoteAugmentedAutofillServiceLocked();
+ logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+ focussedId, isAllowlisted, inlineSuggestionsRequest != null);
+ remoteService.onRequestAutofillLocked(id, mClient,
+ taskId, mComponentName, mActivityToken,
+ AutofillId.withoutSession(focussedId), currentValue,
+ inlineSuggestionsRequest,
+ new AugmentedAutofillInlineSuggestionsResponseCallback(this),
+ new AugmentedAutofillErrorCallback(this),
+ mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
+ }
+ }
+
+ private void onAugmentedAutofillErrorCallback() {
+ synchronized (mLock) {
+ cancelAugmentedAutofillLocked();
+
+ // Also cancel augmented in IME
+ mInlineSessionController.setInlineFillUiLocked(
+ InlineFillUi.emptyUi(mCurrentViewId));
+ }
+ }
+
@GuardedBy("mLock")
private void cancelAugmentedAutofillLocked() {
final RemoteAugmentedAutofillService remoteService = mService
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 6b99494..a1ccade 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1415,6 +1415,30 @@
mCrossDeviceSyncController.syncMessageToDevice(associationId, message);
}
}
+
+ @Override
+ public void sendCrossDeviceSyncMessageToAllDevices(int userId, byte[] message) {
+ if (CompanionDeviceConfig.isEnabled(
+ CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+ mCrossDeviceSyncController.syncMessageToAllDevicesForUserId(userId, message);
+ }
+ }
+
+ @Override
+ public void addSelfOwnedCallId(String callId) {
+ if (CompanionDeviceConfig.isEnabled(
+ CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+ mCrossDeviceSyncController.addSelfOwnedCallId(callId);
+ }
+ }
+
+ @Override
+ public void removeSelfOwnedCallId(String callId) {
+ if (CompanionDeviceConfig.isEnabled(
+ CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
+ mCrossDeviceSyncController.removeSelfOwnedCallId(callId);
+ }
+ }
}
/**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index c5ef4e4..cdf832f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -45,6 +45,15 @@
*/
void sendCrossDeviceSyncMessage(int associationId, byte[] message);
+ /** Sends the provided message to all active associations for the specified user. */
+ void sendCrossDeviceSyncMessageToAllDevices(int userId, byte[] message);
+
+ /** Mark a call id as "self owned" (i.e. this device owns the canonical call). */
+ void addSelfOwnedCallId(String callId);
+
+ /** Unmark a call id as "self owned" (i.e. this device no longer owns the canonical call). */
+ void removeSelfOwnedCallId(String callId);
+
/**
* Requests a sync from an InCallService to CDM, for the given user and call metadata.
*/
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
index 7371824..fac1c89 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -36,7 +36,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
-import java.util.UUID;
+import java.util.Set;
/** Service for Telecom to bind to when call metadata is synced between devices. */
public class CallMetadataSyncConnectionService extends ConnectionService {
@@ -65,11 +65,32 @@
associationId, call.getId()));
if (existingConnection != null) {
existingConnection.update(call);
+ } else {
+ // Check if this is an in-progress id being finalized.
+ CallMetadataSyncConnectionIdentifier key = null;
+ for (Map.Entry<CallMetadataSyncConnectionIdentifier,
+ CallMetadataSyncConnection> e : mActiveConnections.entrySet()) {
+ if (e.getValue().getAssociationId() == associationId
+ && !e.getValue().isIdFinalized()
+ && call.getId().endsWith(e.getValue().getCallId())) {
+ key = e.getKey();
+ break;
+ }
+ }
+ if (key != null) {
+ final CallMetadataSyncConnection connection =
+ mActiveConnections.remove(key);
+ connection.update(call);
+ mActiveConnections.put(
+ new CallMetadataSyncConnectionIdentifier(associationId,
+ call.getId()), connection);
+ }
}
}
// Remove obsolete calls.
mActiveConnections.values().removeIf(connection -> {
- if (associationId == connection.getAssociationId()
+ if (connection.isIdFinalized()
+ && associationId == connection.getAssociationId()
&& !callMetadataSyncData.hasCall(connection.getCallId())) {
connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
return true;
@@ -77,6 +98,17 @@
return false;
});
}
+
+ @Override
+ void cleanUpCallIds(Set<String> callIds) {
+ mActiveConnections.values().removeIf(connection -> {
+ if (callIds.contains(connection.getCallId())) {
+ connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
+ return true;
+ }
+ return false;
+ });
+ }
};
@Override
@@ -95,10 +127,9 @@
ConnectionRequest connectionRequest) {
final int associationId = connectionRequest.getExtras().getInt(
CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
- final CallMetadataSyncData.Call call = connectionRequest.getExtras().getParcelable(
- CrossDeviceSyncController.EXTRA_CALL, CallMetadataSyncData.Call.class);
- // InCallServices outside of framework (like Dialer's) might try to read this, and crash
- // when they can't. Remove it once we're done with it, as well as the other internal ones.
+ final CallMetadataSyncData.Call call = CallMetadataSyncData.Call.fromBundle(
+ connectionRequest.getExtras().getBundle(CrossDeviceSyncController.EXTRA_CALL));
+ call.setDirection(android.companion.Telecom.Call.INCOMING);
connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL);
connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID);
connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
@@ -130,18 +161,26 @@
@Override
public Connection onCreateOutgoingConnection(PhoneAccountHandle phoneAccountHandle,
ConnectionRequest connectionRequest) {
- final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(phoneAccountHandle);
+ final PhoneAccountHandle handle = phoneAccountHandle != null ? phoneAccountHandle
+ : connectionRequest.getAccountHandle();
+ final PhoneAccount phoneAccount = mTelecomManager.getPhoneAccount(handle);
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
- call.setId(UUID.randomUUID().toString());
+ call.setId(
+ connectionRequest.getExtras().getString(CrossDeviceSyncController.EXTRA_CALL_ID));
call.setStatus(android.companion.Telecom.Call.UNKNOWN_STATUS);
final CallMetadataSyncData.CallFacilitator callFacilitator =
- new CallMetadataSyncData.CallFacilitator(phoneAccount.getLabel().toString(),
- phoneAccount.getExtras().getString(
- CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID));
+ new CallMetadataSyncData.CallFacilitator(phoneAccount != null
+ ? phoneAccount.getLabel().toString()
+ : handle.getComponentName().getShortClassName(),
+ phoneAccount != null ? phoneAccount.getExtras().getString(
+ CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID)
+ : handle.getComponentName().getPackageName());
call.setFacilitator(callFacilitator);
+ call.setDirection(android.companion.Telecom.Call.OUTGOING);
+ call.setCallerId(connectionRequest.getAddress().getSchemeSpecificPart());
- final int associationId = connectionRequest.getExtras().getInt(
+ final int associationId = phoneAccount.getExtras().getInt(
CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL);
@@ -160,13 +199,15 @@
CrossDeviceSyncController.createCallControlMessage(callId, action));
}
});
- connection.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
+ connection.setCallerDisplayName(call.getCallerId(), TelecomManager.PRESENTATION_ALLOWED);
+ mCdmsi.addSelfOwnedCallId(call.getId());
mCdmsi.sendCrossDeviceSyncMessage(associationId,
CrossDeviceSyncController.createCallCreateMessage(call.getId(),
connectionRequest.getAddress().toString(),
call.getFacilitator().getIdentifier()));
+ connection.setInitializing();
return connection;
}
@@ -240,6 +281,7 @@
private final int mAssociationId;
private final CallMetadataSyncData.Call mCall;
private final CallMetadataSyncConnectionCallback mCallback;
+ private boolean mIsIdFinalized;
CallMetadataSyncConnection(TelecomManager telecomManager, AudioManager audioManager,
int associationId, CallMetadataSyncData.Call call,
@@ -259,6 +301,10 @@
return mAssociationId;
}
+ public boolean isIdFinalized() {
+ return mIsIdFinalized;
+ }
+
private void initialize() {
final int status = mCall.getStatus();
if (status == android.companion.Telecom.Call.RINGING_SILENCED) {
@@ -273,6 +319,8 @@
setOnHold();
} else if (state == Call.STATE_DISCONNECTED) {
setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
+ } else if (state == Call.STATE_DIALING) {
+ setDialing();
} else {
setInitialized();
}
@@ -307,6 +355,10 @@
}
private void update(CallMetadataSyncData.Call call) {
+ if (!mIsIdFinalized) {
+ mCall.setId(call.getId());
+ mIsIdFinalized = true;
+ }
final int status = call.getStatus();
if (status == android.companion.Telecom.Call.RINGING_SILENCED
&& mCall.getStatus() != android.companion.Telecom.Call.RINGING_SILENCED) {
@@ -323,6 +375,8 @@
setOnHold();
} else if (state == Call.STATE_DISCONNECTED) {
setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
+ } else if (state == Call.STATE_DIALING) {
+ setDialing();
} else {
Slog.e(TAG, "Could not update call to unknown state");
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
index d8621cb..74641a4 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
@@ -16,10 +16,8 @@
package com.android.server.companion.datatransfer.contextsync;
-import android.annotation.NonNull;
import android.companion.ContextSyncMessage;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.os.Bundle;
import java.util.ArrayList;
import java.util.Collection;
@@ -74,9 +72,10 @@
return mCallFacilitators;
}
- public static class CallFacilitator implements Parcelable {
+ public static class CallFacilitator {
private String mName;
private String mIdentifier;
+ private boolean mIsTel;
CallFacilitator() {}
@@ -85,16 +84,6 @@
mIdentifier = identifier;
}
- CallFacilitator(Parcel parcel) {
- this(parcel.readString(), parcel.readString());
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int parcelableFlags) {
- parcel.writeString(mName);
- parcel.writeString(mIdentifier);
- }
-
public String getName() {
return mName;
}
@@ -103,6 +92,10 @@
return mIdentifier;
}
+ public boolean isTel() {
+ return mIsTel;
+ }
+
public void setName(String name) {
mName = name;
}
@@ -111,25 +104,9 @@
mIdentifier = identifier;
}
- @Override
- public int describeContents() {
- return 0;
+ public void setIsTel(boolean isTel) {
+ mIsTel = isTel;
}
-
- @NonNull
- public static final Parcelable.Creator<CallFacilitator> CREATOR =
- new Parcelable.Creator<>() {
-
- @Override
- public CallFacilitator createFromParcel(Parcel source) {
- return new CallFacilitator(source);
- }
-
- @Override
- public CallFacilitator[] newArray(int size) {
- return new CallFacilitator[size];
- }
- };
}
public static class CallControlRequest {
@@ -183,40 +160,57 @@
}
}
- public static class Call implements Parcelable {
+ public static class Call {
+
+ private static final String EXTRA_CALLER_ID =
+ "com.android.server.companion.datatransfer.contextsync.extra.CALLER_ID";
+ private static final String EXTRA_APP_ICON =
+ "com.android.server.companion.datatransfer.contextsync.extra.APP_ICON";
+ private static final String EXTRA_FACILITATOR_NAME =
+ "com.android.server.companion.datatransfer.contextsync.extra.FACILITATOR_NAME";
+ private static final String EXTRA_FACILITATOR_ID =
+ "com.android.server.companion.datatransfer.contextsync.extra.FACILITATOR_ID";
+ private static final String EXTRA_STATUS =
+ "com.android.server.companion.datatransfer.contextsync.extra.STATUS";
+ private static final String EXTRA_DIRECTION =
+ "com.android.server.companion.datatransfer.contextsync.extra.DIRECTION";
+ private static final String EXTRA_CONTROLS =
+ "com.android.server.companion.datatransfer.contextsync.extra.CONTROLS";
private String mId;
private String mCallerId;
private byte[] mAppIcon;
private CallFacilitator mFacilitator;
private int mStatus;
+ private int mDirection;
private final Set<Integer> mControls = new HashSet<>();
- public static Call fromParcel(Parcel parcel) {
+ public static Call fromBundle(Bundle bundle) {
final Call call = new Call();
- call.setId(parcel.readString());
- call.setCallerId(parcel.readString());
- call.setAppIcon(parcel.readBlob());
- call.setFacilitator(parcel.readParcelable(CallFacilitator.class.getClassLoader(),
- CallFacilitator.class));
- call.setStatus(parcel.readInt());
- final int numberOfControls = parcel.readInt();
- for (int i = 0; i < numberOfControls; i++) {
- call.addControl(parcel.readInt());
+ if (bundle != null) {
+ call.setId(bundle.getString(CrossDeviceSyncController.EXTRA_CALL_ID));
+ call.setCallerId(bundle.getString(EXTRA_CALLER_ID));
+ call.setAppIcon(bundle.getByteArray(EXTRA_APP_ICON));
+ final String facilitatorName = bundle.getString(EXTRA_FACILITATOR_NAME);
+ final String facilitatorIdentifier = bundle.getString(EXTRA_FACILITATOR_ID);
+ call.setFacilitator(new CallFacilitator(facilitatorName, facilitatorIdentifier));
+ call.setStatus(bundle.getInt(EXTRA_STATUS));
+ call.setDirection(bundle.getInt(EXTRA_DIRECTION));
+ call.setControls(new HashSet<>(bundle.getIntegerArrayList(EXTRA_CONTROLS)));
}
return call;
}
- @Override
- public void writeToParcel(Parcel parcel, int parcelableFlags) {
- parcel.writeString(mId);
- parcel.writeString(mCallerId);
- parcel.writeBlob(mAppIcon);
- parcel.writeParcelable(mFacilitator, parcelableFlags);
- parcel.writeInt(mStatus);
- parcel.writeInt(mControls.size());
- for (int control : mControls) {
- parcel.writeInt(control);
- }
+ public Bundle writeToBundle() {
+ final Bundle bundle = new Bundle();
+ bundle.putString(CrossDeviceSyncController.EXTRA_CALL_ID, mId);
+ bundle.putString(EXTRA_CALLER_ID, mCallerId);
+ bundle.putByteArray(EXTRA_APP_ICON, mAppIcon);
+ bundle.putString(EXTRA_FACILITATOR_NAME, mFacilitator.getName());
+ bundle.putString(EXTRA_FACILITATOR_ID, mFacilitator.getIdentifier());
+ bundle.putInt(EXTRA_STATUS, mStatus);
+ bundle.putInt(EXTRA_DIRECTION, mDirection);
+ bundle.putIntegerArrayList(EXTRA_CONTROLS, new ArrayList<>(mControls));
+ return bundle;
}
void setId(String id) {
@@ -239,6 +233,10 @@
mStatus = status;
}
+ void setDirection(int direction) {
+ mDirection = direction;
+ }
+
void addControl(int control) {
mControls.add(control);
}
@@ -268,6 +266,10 @@
return mStatus;
}
+ int getDirection() {
+ return mDirection;
+ }
+
Set<Integer> getControls() {
return mControls;
}
@@ -288,23 +290,5 @@
public int hashCode() {
return Objects.hashCode(mId);
}
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @NonNull public static final Parcelable.Creator<Call> CREATOR = new Parcelable.Creator<>() {
-
- @Override
- public Call createFromParcel(Parcel source) {
- return Call.fromParcel(source);
- }
-
- @Override
- public Call[] newArray(int size) {
- return new Call[size];
- }
- };
}
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
index b46d5d3..e6d36a5 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
@@ -79,15 +79,20 @@
int callControlAction) {
final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
mCurrentCalls.values());
- if (crossDeviceCall == null) {
- return;
- }
switch (callControlAction) {
case android.companion.Telecom.ACCEPT:
- crossDeviceCall.doAccept();
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doAccept();
+ } else {
+ Slog.w(TAG, "Failed to process accept action; no matching call");
+ }
break;
case android.companion.Telecom.REJECT:
- crossDeviceCall.doReject();
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doReject();
+ } else {
+ Slog.w(TAG, "Failed to process reject action; no matching call");
+ }
break;
case android.companion.Telecom.SILENCE:
doSilence();
@@ -99,13 +104,25 @@
doUnmute();
break;
case android.companion.Telecom.END:
- crossDeviceCall.doEnd();
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doEnd();
+ } else {
+ Slog.w(TAG, "Failed to process end action; no matching call");
+ }
break;
case android.companion.Telecom.PUT_ON_HOLD:
- crossDeviceCall.doPutOnHold();
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doPutOnHold();
+ } else {
+ Slog.w(TAG, "Failed to process hold action; no matching call");
+ }
break;
case android.companion.Telecom.TAKE_OFF_HOLD:
- crossDeviceCall.doTakeOffHold();
+ if (crossDeviceCall != null) {
+ crossDeviceCall.doTakeOffHold();
+ } else {
+ Slog.w(TAG, "Failed to process unhold action; no matching call");
+ }
break;
default:
}
@@ -188,6 +205,8 @@
&& mNumberOfActiveSyncAssociations > 0) {
mCurrentCalls.remove(call);
call.unregisterCallback(mTelecomCallback);
+ mCdmsi.removeSelfOwnedCallId(call.getDetails().getExtras().getString(
+ CrossDeviceSyncController.EXTRA_CALL_ID));
sync(getUserId());
}
}
@@ -196,8 +215,9 @@
public void onMuteStateChanged(boolean isMuted) {
if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
&& mNumberOfActiveSyncAssociations > 0) {
- mCurrentCalls.values().forEach(call -> call.updateMuted(isMuted));
- sync(getUserId());
+ mCdmsi.sendCrossDeviceSyncMessageToAllDevices(getUserId(),
+ CrossDeviceSyncController.createCallControlMessage(null, isMuted
+ ? android.companion.Telecom.MUTE : android.companion.Telecom.UNMUTE));
}
}
@@ -205,8 +225,9 @@
public void onSilenceRinger() {
if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
&& mNumberOfActiveSyncAssociations > 0) {
- mCurrentCalls.values().forEach(call -> call.updateSilencedIfRinging());
- sync(getUserId());
+ mCdmsi.sendCrossDeviceSyncMessageToAllDevices(getUserId(),
+ CrossDeviceSyncController.createCallControlMessage(null,
+ android.companion.Telecom.SILENCE));
}
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
index fec6923..e8392d2 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -25,8 +25,10 @@
import android.net.Uri;
import android.telecom.Call;
import android.telecom.CallAudioState;
+import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -39,9 +41,11 @@
public class CrossDeviceCall {
private static final String TAG = "CrossDeviceCall";
+ private static final String SEPARATOR = "::";
private final String mId;
private final Call mCall;
+ private final int mUserId;
@VisibleForTesting boolean mIsEnterprise;
private final String mCallingAppPackageName;
private String mCallingAppName;
@@ -52,6 +56,7 @@
private String mContactDisplayName;
private Uri mHandle;
private int mHandlePresentation;
+ private int mDirection;
private boolean mIsMuted;
private final Set<Integer> mControls = new HashSet<>();
private final boolean mIsCallPlacedByContextSync;
@@ -73,22 +78,24 @@
? callDetails.getIntentExtras().getString(CrossDeviceSyncController.EXTRA_CALL_ID)
: null;
final String generatedId = UUID.randomUUID().toString();
- mId = predefinedId != null ? (generatedId + predefinedId) : generatedId;
+ mId = predefinedId != null ? (generatedId + SEPARATOR + predefinedId) : generatedId;
if (call != null) {
call.putExtra(CrossDeviceSyncController.EXTRA_CALL_ID, mId);
}
- mIsCallPlacedByContextSync =
- new ComponentName(context, CallMetadataSyncConnectionService.class)
- .equals(callDetails.getAccountHandle().getComponentName());
- mCallingAppPackageName =
- callDetails.getAccountHandle().getComponentName().getPackageName();
+ final PhoneAccountHandle handle = callDetails.getAccountHandle();
+ mUserId = handle != null ? handle.getUserHandle().getIdentifier() : -1;
+ mIsCallPlacedByContextSync = handle != null
+ && new ComponentName(context, CallMetadataSyncConnectionService.class)
+ .equals(handle.getComponentName());
+ mCallingAppPackageName = handle != null
+ ? callDetails.getAccountHandle().getComponentName().getPackageName() : "";
mIsEnterprise = (callDetails.getCallProperties() & Call.Details.PROPERTY_ENTERPRISE_CALL)
== Call.Details.PROPERTY_ENTERPRISE_CALL;
final PackageManager packageManager = context.getPackageManager();
try {
final ApplicationInfo applicationInfo = packageManager
- .getApplicationInfo(mCallingAppPackageName,
- PackageManager.ApplicationInfoFlags.of(0));
+ .getApplicationInfoAsUser(mCallingAppPackageName,
+ PackageManager.ApplicationInfoFlags.of(0), mUserId);
mCallingAppName = packageManager.getApplicationLabel(applicationInfo).toString();
mCallingAppIcon = BitmapUtils.renderDrawableToByteArray(
packageManager.getApplicationIcon(applicationInfo));
@@ -128,8 +135,19 @@
mContactDisplayName = callDetails.getContactDisplayName();
mHandle = callDetails.getHandle();
mHandlePresentation = callDetails.getHandlePresentation();
+ final int direction = callDetails.getCallDirection();
+ if (direction == Call.Details.DIRECTION_INCOMING) {
+ mDirection = android.companion.Telecom.Call.INCOMING;
+ } else if (direction == Call.Details.DIRECTION_OUTGOING) {
+ mDirection = android.companion.Telecom.Call.OUTGOING;
+ } else {
+ mDirection = android.companion.Telecom.Call.UNKNOWN_DIRECTION;
+ }
mStatus = convertStateToStatus(callDetails.getState());
mControls.clear();
+ if (mStatus == android.companion.Telecom.Call.DIALING) {
+ mControls.add(android.companion.Telecom.END);
+ }
if (mStatus == android.companion.Telecom.Call.RINGING
|| mStatus == android.companion.Telecom.Call.RINGING_SILENCED) {
mControls.add(android.companion.Telecom.ACCEPT);
@@ -170,6 +188,8 @@
return android.companion.Telecom.Call.RINGING_SIMULATED;
case Call.STATE_DISCONNECTED:
return android.companion.Telecom.Call.DISCONNECTED;
+ case Call.STATE_DIALING:
+ return android.companion.Telecom.Call.DIALING;
default:
Slog.e(TAG, "Couldn't resolve state to status: " + callState);
return android.companion.Telecom.Call.UNKNOWN_STATUS;
@@ -195,6 +215,8 @@
return Call.STATE_SIMULATED_RINGING;
case android.companion.Telecom.Call.DISCONNECTED:
return Call.STATE_DISCONNECTED;
+ case android.companion.Telecom.Call.DIALING:
+ return Call.STATE_DIALING;
case android.companion.Telecom.Call.UNKNOWN_STATUS:
default:
return Call.STATE_NEW;
@@ -209,6 +231,10 @@
return mCall;
}
+ public int getUserId() {
+ return mUserId;
+ }
+
public String getCallingAppName() {
return mCallingAppName;
}
@@ -231,11 +257,11 @@
// Cannot use any contact information.
return getNonContactString();
}
- return mContactDisplayName != null ? mContactDisplayName : getNonContactString();
+ return TextUtils.isEmpty(mContactDisplayName) ? getNonContactString() : mContactDisplayName;
}
private String getNonContactString() {
- if (mCallerDisplayName != null
+ if (!TextUtils.isEmpty(mCallerDisplayName)
&& mCallerDisplayNamePresentation == TelecomManager.PRESENTATION_ALLOWED) {
return mCallerDisplayName;
}
@@ -250,6 +276,10 @@
return mStatus;
}
+ public int getDirection() {
+ return mDirection;
+ }
+
public Set<Integer> getControls() {
return mControls;
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
index bf82f3f..3169459 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
@@ -154,6 +154,7 @@
Slog.w(TAG, "No callback to report removed transport");
}
}
+ clearInProgressCalls(associationInfo.getId());
} else {
// Stable association!
final boolean systemBlocked = isAssociationBlocked(associationInfo);
@@ -187,6 +188,7 @@
// will get stale)
syncMessageToDevice(associationInfo.getId(),
createEmptyMessage());
+ clearInProgressCalls(associationInfo.getId());
}
}
}
@@ -201,9 +203,14 @@
return;
}
final CallMetadataSyncData processedData = processTelecomDataFromSync(data);
- mPhoneAccountManager.updateFacilitators(associationId, processedData);
- mCallManager.updateCalls(associationId, processedData);
- processCallCreateRequests(processedData);
+ final boolean isRequest = processedData.getCallControlRequests().size() != 0
+ || processedData.getCallCreateRequests().size() != 0;
+ if (!isRequest) {
+ mPhoneAccountManager.updateFacilitators(associationId, processedData);
+ mCallManager.updateCalls(associationId, processedData);
+ } else {
+ processCallCreateRequests(processedData);
+ }
if (mInCallServiceCallbackRef == null
&& mConnectionServiceCallbackRef == null) {
Slog.w(TAG, "No callback to process context sync message");
@@ -213,8 +220,10 @@
mInCallServiceCallbackRef != null ? mInCallServiceCallbackRef.get()
: null;
if (inCallServiceCallback != null) {
- inCallServiceCallback.processContextSyncMessage(associationId,
- processedData);
+ if (isRequest) {
+ inCallServiceCallback.processContextSyncMessage(associationId,
+ processedData);
+ }
} else {
// This is dead; get rid of it lazily
mInCallServiceCallbackRef = null;
@@ -224,8 +233,10 @@
mConnectionServiceCallbackRef != null
? mConnectionServiceCallbackRef.get() : null;
if (connectionServiceCallback != null) {
- connectionServiceCallback.processContextSyncMessage(associationId,
- processedData);
+ if (!isRequest) {
+ connectionServiceCallback.processContextSyncMessage(associationId,
+ processedData);
+ }
} else {
// This is dead; get rid of it lazily
mConnectionServiceCallbackRef = null;
@@ -236,6 +247,15 @@
mCallManager = new CallManager(mContext, mPhoneAccountManager);
}
+ private void clearInProgressCalls(int associationId) {
+ final Set<String> removedIds = mCallManager.clearCallIdsForAssociationId(associationId);
+ final CrossDeviceSyncControllerCallback connectionServiceCallback =
+ mConnectionServiceCallbackRef != null ? mConnectionServiceCallbackRef.get() : null;
+ if (connectionServiceCallback != null) {
+ connectionServiceCallback.cleanUpCallIds(removedIds);
+ }
+ }
+
private static boolean isAssociationBlocked(AssociationInfo info) {
return (info.getSystemDataSyncFlags() & CompanionDeviceManager.FLAG_CALL_METADATA)
!= CompanionDeviceManager.FLAG_CALL_METADATA;
@@ -274,6 +294,7 @@
if (FACILITATOR_ID_SYSTEM.equals(request.getFacilitator().getIdentifier())) {
if (request.getAddress() != null && request.getAddress().startsWith(
PhoneAccount.SCHEME_TEL)) {
+ mCallManager.addSelfOwnedCallId(request.getId());
// Remove all the non-numbers (dashes, parens, scheme)
final Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL,
request.getAddress().replaceAll("\\D+", ""), /* fragment= */ null);
@@ -382,6 +403,38 @@
new int[]{associationId});
}
+ /** Sync message to all associated devices. */
+ public void syncMessageToAllDevicesForUserId(int userId, byte[] message) {
+ final Set<Integer> associationIds = new HashSet<>();
+ for (AssociationInfo associationInfo : mConnectedAssociations) {
+ if (associationInfo.getUserId() == userId && !isAssociationBlocked(associationInfo)) {
+ associationIds.add(associationInfo.getId());
+ }
+ }
+ if (associationIds.isEmpty()) {
+ Slog.w(TAG, "No eligible devices to sync to");
+ return;
+ }
+
+ mCompanionTransportManager.sendMessage(MESSAGE_REQUEST_CONTEXT_SYNC, message,
+ associationIds.stream().mapToInt(Integer::intValue).toArray());
+ }
+
+ /**
+ * Mark a call id as owned (i.e. this device owns the canonical call). Note that both sides will
+ * own outgoing calls that were placed on behalf of another device.
+ */
+ public void addSelfOwnedCallId(String callId) {
+ mCallManager.addSelfOwnedCallId(callId);
+ }
+
+ /** Unmark a call id as owned (i.e. this device no longer owns the canonical call). */
+ public void removeSelfOwnedCallId(String callId) {
+ if (callId != null) {
+ mCallManager.removeSelfOwnedCallId(callId);
+ }
+ }
+
@VisibleForTesting
CallMetadataSyncData processTelecomDataFromSync(byte[] data) {
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
@@ -430,8 +483,10 @@
pis.end(requestsToken);
} else if (pis.getFieldNumber() == (int) Telecom.FACILITATORS) {
final long facilitatorsToken = pis.start(Telecom.FACILITATORS);
- callMetadataSyncData.addFacilitator(
- processFacilitatorDataFromSync(pis));
+ final CallMetadataSyncData.CallFacilitator facilitator =
+ processFacilitatorDataFromSync(pis);
+ facilitator.setIsTel(true);
+ callMetadataSyncData.addFacilitator(facilitator);
pis.end(facilitatorsToken);
} else {
Slog.e(TAG, "Unhandled field in Telecom:"
@@ -561,6 +616,9 @@
case (int) Telecom.Call.STATUS:
call.setStatus(pis.readInt(Telecom.Call.STATUS));
break;
+ case (int) Telecom.Call.DIRECTION:
+ call.setDirection(pis.readInt(Telecom.Call.DIRECTION));
+ break;
case (int) Telecom.Call.CONTROLS:
call.addControl(pis.readInt(Telecom.Call.CONTROLS));
break;
@@ -578,15 +636,15 @@
pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
for (CrossDeviceCall call : calls) {
- if (call.isCallPlacedByContextSync()) {
- // Do not sync any calls which our "ours" as that would be duplicative.
+ if (call.isCallPlacedByContextSync() || mCallManager.isExternallyOwned(call.getId())) {
+ // Do not sync any of "our" calls, nor external calls, as that would be duplicative.
continue;
}
final long callsToken = pos.start(Telecom.CALLS);
pos.write(Telecom.Call.ID, call.getId());
final long originToken = pos.start(Telecom.Call.ORIGIN);
pos.write(Telecom.Call.Origin.CALLER_ID,
- call.getReadableCallerId(isAdminBlocked(userId)));
+ call.getReadableCallerId(isAdminBlocked(call.getUserId())));
pos.write(Telecom.Call.Origin.APP_ICON, call.getCallingAppIcon());
final long facilitatorToken = pos.start(Telecom.Call.Origin.FACILITATOR);
pos.write(Telecom.CallFacilitator.NAME, call.getCallingAppName());
@@ -594,6 +652,7 @@
pos.end(facilitatorToken);
pos.end(originToken);
pos.write(Telecom.Call.STATUS, call.getStatus());
+ pos.write(Telecom.Call.DIRECTION, call.getDirection());
for (int control : call.getControls()) {
pos.write(Telecom.Call.CONTROLS, control);
}
@@ -658,6 +717,9 @@
@VisibleForTesting
static class CallManager {
+ @VisibleForTesting final Set<String> mSelfOwnedCalls = new HashSet<>();
+ @VisibleForTesting final Set<String> mExternallyOwnedCalls = new HashSet<>();
+
@VisibleForTesting final Map<Integer, Set<String>> mCallIds = new HashMap<>();
private final TelecomManager mTelecomManager;
private final PhoneAccountManager mPhoneAccountManager;
@@ -678,20 +740,63 @@
for (CallMetadataSyncData.Call currentCall : data.getCalls()) {
if (!oldCallIds.contains(currentCall.getId())
- && currentCall.getFacilitator() != null) {
+ && currentCall.getFacilitator() != null
+ && !isSelfOwned(currentCall.getId())) {
+ mExternallyOwnedCalls.add(currentCall.getId());
final Bundle extras = new Bundle();
extras.putInt(EXTRA_ASSOCIATION_ID, associationId);
extras.putBoolean(EXTRA_IS_REMOTE_ORIGIN, true);
- extras.putParcelable(EXTRA_CALL, currentCall);
+ extras.putBundle(EXTRA_CALL, currentCall.writeToBundle());
extras.putString(EXTRA_CALL_ID, currentCall.getId());
extras.putByteArray(EXTRA_FACILITATOR_ICON, currentCall.getAppIcon());
- final PhoneAccountHandle handle = mPhoneAccountManager.getPhoneAccountHandle(
- associationId, currentCall.getFacilitator().getIdentifier());
- mTelecomManager.addNewIncomingCall(handle, extras);
+ final PhoneAccountHandle handle =
+ mPhoneAccountManager.getPhoneAccountHandle(
+ associationId,
+ currentCall.getFacilitator().getIdentifier());
+ if (currentCall.getDirection() == android.companion.Telecom.Call.INCOMING) {
+ mTelecomManager.addNewIncomingCall(handle, extras);
+ } else if (currentCall.getDirection()
+ == android.companion.Telecom.Call.OUTGOING) {
+ final Bundle wrappedExtras = new Bundle();
+ wrappedExtras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
+ extras);
+ wrappedExtras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ handle);
+ final String address = currentCall.getCallerId();
+ if (address != null) {
+ mTelecomManager.placeCall(Uri.fromParts(PhoneAccount.SCHEME_SIP,
+ address, /* fragment= */ null), wrappedExtras);
+ }
+ }
}
}
mCallIds.put(associationId, newCallIds);
}
+
+ Set<String> clearCallIdsForAssociationId(int associationId) {
+ return mCallIds.remove(associationId);
+ }
+
+ void addSelfOwnedCallId(String callId) {
+ mSelfOwnedCalls.add(callId);
+ }
+
+ void removeSelfOwnedCallId(String callId) {
+ mSelfOwnedCalls.remove(callId);
+ }
+
+ boolean isExternallyOwned(String callId) {
+ return mExternallyOwnedCalls.contains(callId);
+ }
+
+ private boolean isSelfOwned(String currentCallId) {
+ for (String selfOwnedCallId : mSelfOwnedCalls) {
+ if (currentCallId.endsWith(selfOwnedCallId)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
static class PhoneAccountManager {
@@ -745,7 +850,8 @@
new PhoneAccountHandleIdentifier(associationId,
facilitator.getIdentifier());
if (!mPhoneAccountHandles.containsKey(phoneAccountHandleIdentifier)) {
- registerPhoneAccount(phoneAccountHandleIdentifier, facilitator.getName());
+ registerPhoneAccount(phoneAccountHandleIdentifier, facilitator.getName(),
+ facilitator.isTel());
}
}
}
@@ -755,7 +861,7 @@
* synced device, and records it in the local {@link #mPhoneAccountHandles} map.
*/
private void registerPhoneAccount(PhoneAccountHandleIdentifier handleIdentifier,
- String humanReadableAppName) {
+ String humanReadableAppName, boolean isTel) {
if (mPhoneAccountHandles.containsKey(handleIdentifier)) {
// Already exists!
return;
@@ -765,7 +871,8 @@
UUID.randomUUID().toString());
mPhoneAccountHandles.put(handleIdentifier, handle);
final PhoneAccount phoneAccount = createPhoneAccount(handle, humanReadableAppName,
- handleIdentifier.getAppIdentifier());
+ handleIdentifier.getAppIdentifier(), handleIdentifier.getAssociationId(),
+ isTel);
mTelecomManager.registerPhoneAccount(phoneAccount);
mTelecomManager.enablePhoneAccount(mPhoneAccountHandles.get(handleIdentifier), true);
}
@@ -781,11 +888,16 @@
@VisibleForTesting
static PhoneAccount createPhoneAccount(PhoneAccountHandle handle,
String humanReadableAppName,
- String appIdentifier) {
+ String appIdentifier,
+ int associationId,
+ boolean isTel) {
final Bundle extras = new Bundle();
extras.putString(EXTRA_CALL_FACILITATOR_ID, appIdentifier);
+ extras.putInt(EXTRA_ASSOCIATION_ID, associationId);
return new PhoneAccount.Builder(handle, humanReadableAppName)
.setExtras(extras)
+ .setSupportedUriSchemes(List.of(isTel ? PhoneAccount.SCHEME_TEL
+ : PhoneAccount.SCHEME_SIP))
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
| PhoneAccount.CAPABILITY_CONNECTION_MANAGER).build();
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
index 8a0ba27..6764830 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
@@ -21,6 +21,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
/** Callback for call metadata syncing. */
public abstract class CrossDeviceSyncControllerCallback {
@@ -40,4 +41,7 @@
void requestCrossDeviceSync(AssociationInfo associationInfo) {}
void updateNumberOfActiveSyncAssociations(int userId, boolean added) {}
+
+ /** Clean up any remaining state for the given calls. */
+ void cleanUpCallIds(Set<String> callIds) {}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 51359ad..9ff0746 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -88,11 +88,14 @@
import android.view.contentcapture.IDataShareWriteAdapter;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.GlobalWhitelistState;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionPackageManager;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -117,7 +120,7 @@
* with other sources to provide contextual data in other areas of the system
* such as Autofill.
*/
-public final class ContentCaptureManagerService extends
+public class ContentCaptureManagerService extends
AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
@@ -205,6 +208,8 @@
final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
new GlobalContentCaptureOptions();
+ @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
public ContentCaptureManagerService(@NonNull Context context) {
super(context, new FrameworkResourcesServiceNameResolver(context,
com.android.internal.R.string.config_defaultContentCaptureService),
@@ -242,6 +247,14 @@
mServiceNameResolver.getServiceName(userId),
mServiceNameResolver.isTemporary(userId));
}
+
+ if (getEnableContentProtectionReceiverLocked()) {
+ mContentProtectionBlocklistManager = createContentProtectionBlocklistManager(context);
+ mContentProtectionBlocklistManager.updateBlocklist(
+ mDevCfgContentProtectionAppsBlocklistSize);
+ } else {
+ mContentProtectionBlocklistManager = null;
+ }
}
@Override // from AbstractMasterSystemService
@@ -397,7 +410,9 @@
}
}
- private void setFineTuneParamsFromDeviceConfig() {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ protected void setFineTuneParamsFromDeviceConfig() {
synchronized (mLock) {
mDevCfgMaxBufferSize =
DeviceConfig.getInt(
@@ -443,6 +458,8 @@
ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
+ // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
+ // it immutable at this point
mDevCfgContentProtectionBufferSize =
DeviceConfig.getInt(
DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -754,6 +771,25 @@
mGlobalContentCaptureOptions.dump(prefix2, pw);
}
+ /**
+ * Used by the constructor in order to be able to override the value in the tests.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @GuardedBy("mLock")
+ protected boolean getEnableContentProtectionReceiverLocked() {
+ return mDevCfgEnableContentProtectionReceiver;
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @NonNull
+ protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager(
+ @NonNull Context context) {
+ return new ContentProtectionBlocklistManager(new ContentProtectionPackageManager(context));
+ }
+
final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
@Override
@@ -1075,14 +1111,21 @@
@GuardedBy("mGlobalWhitelistStateLock")
public ContentCaptureOptions getOptions(@UserIdInt int userId,
@NonNull String packageName) {
- boolean packageWhitelisted;
+ boolean isContentCaptureReceiverEnabled;
+ boolean isContentProtectionReceiverEnabled;
ArraySet<ComponentName> whitelistedComponents = null;
+
synchronized (mGlobalWhitelistStateLock) {
- packageWhitelisted = isWhitelisted(userId, packageName);
- if (!packageWhitelisted) {
- // Full package is not allowlisted: check individual components first
+ isContentCaptureReceiverEnabled =
+ isContentCaptureReceiverEnabled(userId, packageName);
+ isContentProtectionReceiverEnabled =
+ isContentProtectionReceiverEnabled(packageName);
+
+ if (!isContentCaptureReceiverEnabled) {
+ // Full package is not allowlisted: check individual components next
whitelistedComponents = getWhitelistedComponents(userId, packageName);
- if (whitelistedComponents == null
+ if (!isContentProtectionReceiverEnabled
+ && whitelistedComponents == null
&& packageName.equals(mServicePackages.get(userId))) {
// No components allowlisted either, but let it go because it's the
// service's own package
@@ -1101,7 +1144,9 @@
}
}
- if (!packageWhitelisted && whitelistedComponents == null) {
+ if (!isContentCaptureReceiverEnabled
+ && !isContentProtectionReceiverEnabled
+ && whitelistedComponents == null) {
// No can do!
if (verbose) {
Slog.v(TAG, "getOptionsForPackage(" + packageName + "): not whitelisted");
@@ -1118,9 +1163,9 @@
mDevCfgTextChangeFlushingFrequencyMs,
mDevCfgLogHistorySize,
mDevCfgDisableFlushForViewTreeAppearing,
- /* enableReceiver= */ true,
+ isContentCaptureReceiverEnabled || whitelistedComponents != null,
new ContentCaptureOptions.ContentProtectionOptions(
- mDevCfgEnableContentProtectionReceiver,
+ isContentProtectionReceiverEnabled,
mDevCfgContentProtectionBufferSize),
whitelistedComponents);
if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
@@ -1141,6 +1186,35 @@
}
}
}
+
+ @Override // from GlobalWhitelistState
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
+ return isContentCaptureReceiverEnabled(userId, packageName)
+ || isContentProtectionReceiverEnabled(packageName);
+ }
+
+ @Override // from GlobalWhitelistState
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+ return super.isWhitelisted(userId, componentName)
+ || isContentProtectionReceiverEnabled(componentName.getPackageName());
+ }
+
+ private boolean isContentCaptureReceiverEnabled(
+ @UserIdInt int userId, @NonNull String packageName) {
+ return super.isWhitelisted(userId, packageName);
+ }
+
+ private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) {
+ if (mContentProtectionBlocklistManager == null) {
+ return false;
+ }
+ synchronized (mLock) {
+ if (!mDevCfgEnableContentProtectionReceiver) {
+ return false;
+ }
+ }
+ return mContentProtectionBlocklistManager.isAllowed(packageName);
+ }
}
private static class DataShareCallbackDelegate extends IDataShareCallback.Stub {
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
index 715cf9a..a0fd28b 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
@@ -35,7 +35,7 @@
*
* @hide
*/
-class ContentProtectionBlocklistManager {
+public class ContentProtectionBlocklistManager {
private static final String TAG = "ContentProtectionBlocklistManager";
@@ -46,7 +46,7 @@
@Nullable private Set<String> mPackageNameBlocklist;
- protected ContentProtectionBlocklistManager(
+ public ContentProtectionBlocklistManager(
@NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
mContentProtectionPackageManager = contentProtectionPackageManager;
}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
index 1847e5d..4ebac07 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
@@ -43,7 +43,7 @@
@NonNull private final PackageManager mPackageManager;
- ContentProtectionPackageManager(@NonNull Context context) {
+ public ContentProtectionPackageManager(@NonNull Context context) {
mPackageManager = context.getPackageManager();
}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
new file mode 100644
index 0000000..7b34e73
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.service.contentcapture.ContentCaptureService;
+import android.util.Slog;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureDirectManager;
+
+import com.android.internal.infra.ServiceConnector;
+
+import java.time.Duration;
+
+/**
+ * Connector for the remote content protection service.
+ *
+ * @hide
+ */
+class RemoteContentProtectionService extends ServiceConnector.Impl<IContentCaptureDirectManager> {
+
+ private static final String TAG = RemoteContentProtectionService.class.getSimpleName();
+
+ private static final Duration AUTO_DISCONNECT_TIMEOUT = Duration.ofSeconds(3);
+
+ @NonNull private final ComponentName mComponentName;
+
+ protected RemoteContentProtectionService(
+ @NonNull Context context,
+ @NonNull ComponentName componentName,
+ int userId,
+ boolean bindAllowInstant) {
+ super(
+ context,
+ new Intent(ContentCaptureService.PROTECTION_SERVICE_INTERFACE)
+ .setComponent(componentName),
+ bindAllowInstant ? Context.BIND_ALLOW_INSTANT : 0,
+ userId,
+ IContentCaptureDirectManager.Stub::asInterface);
+ mComponentName = componentName;
+ }
+
+ @Override // from ServiceConnector.Impl
+ protected long getAutoDisconnectTimeoutMs() {
+ return AUTO_DISCONNECT_TIMEOUT.toMillis();
+ }
+
+ @Override // from ServiceConnector.Impl
+ protected void onServiceConnectionStatusChanged(
+ @NonNull IContentCaptureDirectManager service, boolean isConnected) {
+ Slog.i(
+ TAG,
+ "Connection status for: "
+ + mComponentName
+ + " changed to: "
+ + (isConnected ? "connected" : "disconnected"));
+ }
+
+ public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+ run(
+ service ->
+ service.sendEvents(
+ events, FLUSH_REASON_LOGIN_DETECTED, /* options= */ null));
+ }
+}
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 2b43ef4..9b4f968 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -406,7 +406,7 @@
* Retrieve the VpnProfileState for the profile provisioned by the given package.
*
* @return the VpnProfileState with current information, or null if there was no profile
- * provisioned by the given package.
+ * provisioned and started by the given package.
* @hide
*/
@Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1bd0675..5c1dad9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1489,12 +1489,10 @@
static final class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
static final int CHANGE_FOREGROUND_SERVICES = 1<<1;
- static final int CHANGE_CAPABILITY = 1<<2;
int changes;
int uid;
int pid;
int processState;
- int capability;
boolean foregroundActivities;
int foregroundServiceTypes;
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 4eedfe2..f6004d7 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -264,7 +264,7 @@
if (oldRecord.resultTo != null) {
try {
oldRecord.mIsReceiverAppRunning = true;
- performReceiveLocked(oldRecord.resultToApp, oldRecord.resultTo,
+ performReceiveLocked(oldRecord, oldRecord.resultToApp, oldRecord.resultTo,
oldRecord.intent,
Activity.RESULT_CANCELED, null, null,
false, false, oldRecord.shareIdentity, oldRecord.userId,
@@ -615,7 +615,9 @@
finishTime - r.receiverTime,
packageState,
r.curApp.info.packageName,
- r.callerPackage);
+ r.callerPackage,
+ r.calculateTypeForLogging(),
+ r.getDeliveryGroupPolicy());
}
if (state == BroadcastRecord.IDLE) {
Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
@@ -742,7 +744,7 @@
}
}
- public void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
+ public void performReceiveLocked(BroadcastRecord r, ProcessRecord app, IIntentReceiver receiver,
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
int receiverUid, int callingUid, String callingPackage,
@@ -795,7 +797,8 @@
BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
dispatchDelay, receiveDelay, 0 /* finish_delay */,
SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
- app != null ? app.info.packageName : null, callingPackage);
+ app != null ? app.info.packageName : null, callingPackage,
+ r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
}
}
@@ -871,7 +874,7 @@
maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
- performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
+ performReceiveLocked(r, filter.receiverList.app, filter.receiverList.receiver,
prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
filter.receiverList.uid, r.callingUid, r.callerPackage,
@@ -1162,7 +1165,7 @@
r.dispatchTime = now;
}
r.mIsReceiverAppRunning = true;
- performReceiveLocked(r.resultToApp, r.resultTo,
+ performReceiveLocked(r, r.resultToApp, r.resultTo,
new Intent(r.intent), r.resultCode,
r.resultData, r.resultExtras, false, false, r.shareIdentity,
r.userId, r.callingUid, r.callingUid, r.callerPackage,
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index bb5fcbe..cbc7540 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -731,8 +731,7 @@
if (mService.shouldIgnoreDeliveryGroupPolicy(r.intent.getAction())) {
return;
}
- final int policy = (r.options != null)
- ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
+ final int policy = r.getDeliveryGroupPolicy();
final BroadcastConsumer broadcastConsumer;
switch (policy) {
case BroadcastOptions.DELIVERY_GROUP_POLICY_ALL:
@@ -1932,7 +1931,8 @@
: SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName,
receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState,
- app != null ? app.info.packageName : null, r.callerPackage);
+ app != null ? app.info.packageName : null, r.callerPackage,
+ r.calculateTypeForLogging(), r.getDeliveryGroupPolicy());
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 863dd63..cfdb133 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -17,6 +17,19 @@
package com.android.server.am;
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_ALARM;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_BACKGROUND;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_DEFERRABLE_UNTIL_ACTIVE;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_FOREGROUND;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_INITIAL_STICKY;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_INTERACTIVE;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_NONE;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_ORDERED;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_PRIORITIZED;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_PUSH_MESSAGE;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_RESULT_TO;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_STICKY;
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;
@@ -35,6 +48,7 @@
import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
+import android.app.BroadcastOptions.DeliveryGroupPolicy;
import android.app.compat.CompatChanges;
import android.content.ComponentName;
import android.content.IIntentReceiver;
@@ -1018,6 +1032,46 @@
}
}
+ int calculateTypeForLogging() {
+ int type = BROADCAST_TYPE_NONE;
+ if (isForeground()) {
+ type |= BROADCAST_TYPE_FOREGROUND;
+ } else {
+ type |= BROADCAST_TYPE_BACKGROUND;
+ }
+ if (alarm) {
+ type |= BROADCAST_TYPE_ALARM;
+ }
+ if (interactive) {
+ type |= BROADCAST_TYPE_INTERACTIVE;
+ }
+ if (ordered) {
+ type |= BROADCAST_TYPE_ORDERED;
+ }
+ if (prioritized) {
+ type |= BROADCAST_TYPE_PRIORITIZED;
+ }
+ if (resultTo != null) {
+ type |= BROADCAST_TYPE_RESULT_TO;
+ }
+ if (deferUntilActive) {
+ type |= BROADCAST_TYPE_DEFERRABLE_UNTIL_ACTIVE;
+ }
+ if (pushMessage) {
+ type |= BROADCAST_TYPE_PUSH_MESSAGE;
+ }
+ if (pushMessageOverQuota) {
+ type |= BROADCAST_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+ }
+ if (sticky) {
+ type |= BROADCAST_TYPE_STICKY;
+ }
+ if (initialSticky) {
+ type |= BROADCAST_TYPE_INITIAL_STICKY;
+ }
+ return type;
+ }
+
public BroadcastRecord maybeStripForHistory() {
if (!intent.canStripForHistory()) {
return this;
@@ -1113,6 +1167,12 @@
return true;
}
+ @DeliveryGroupPolicy
+ int getDeliveryGroupPolicy() {
+ return (options != null) ? options.getDeliveryGroupPolicy()
+ : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
+ }
+
boolean matchesDeliveryGroup(@NonNull BroadcastRecord other) {
return matchesDeliveryGroup(this, other);
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 6c3f01e..a86c2e3 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3188,7 +3188,6 @@
}
if (state.getCurCapability() != state.getSetCapability()) {
- changes |= ActivityManagerService.ProcessChangeItem.CHANGE_CAPABILITY;
state.setSetCapability(state.getCurCapability());
}
@@ -3212,13 +3211,12 @@
mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid);
item.changes |= changes;
item.foregroundActivities = state.hasRepForegroundActivities();
- item.capability = state.getSetCapability();
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"Item " + Integer.toHexString(System.identityHashCode(item))
+ " " + app.toShortString() + ": changes=" + item.changes
+ " foreground=" + item.foregroundActivities
+ " type=" + state.getAdjType() + " source=" + state.getAdjSource()
- + " target=" + state.getAdjTarget() + " capability=" + item.capability);
+ + " target=" + state.getAdjTarget());
}
if (state.isCached() && !state.shouldNotKillOnBgRestrictedAndIdle()) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index d7a5ee9..b86da88 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2205,19 +2205,19 @@
if (preferredCommunicationDevice == null) {
AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
if (defaultDevice != null) {
- mDeviceInventory.setPreferredDevicesForStrategy(
+ mDeviceInventory.setPreferredDevicesForStrategyInt(
mCommunicationStrategyId, Arrays.asList(defaultDevice));
- mDeviceInventory.setPreferredDevicesForStrategy(
+ mDeviceInventory.setPreferredDevicesForStrategyInt(
mAccessibilityStrategyId, Arrays.asList(defaultDevice));
} else {
- mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId);
- mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId);
+ mDeviceInventory.removePreferredDevicesForStrategInt(mCommunicationStrategyId);
+ mDeviceInventory.removePreferredDevicesForStrategInt(mAccessibilityStrategyId);
}
mDeviceInventory.applyConnectedDevicesRoles();
} else {
- mDeviceInventory.setPreferredDevicesForStrategy(
+ mDeviceInventory.setPreferredDevicesForStrategyInt(
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
- mDeviceInventory.setPreferredDevicesForStrategy(
+ mDeviceInventory.setPreferredDevicesForStrategyInt(
mAccessibilityStrategyId, Arrays.asList(preferredCommunicationDevice));
}
onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index a561612..7b2e732 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -67,6 +67,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.stream.Stream;
/**
* Class to manage the inventory of all connected devices.
@@ -324,14 +325,22 @@
mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
pw.println(" " + prefix + "capturePreset:" + capturePreset
+ " devices:" + devices); });
- pw.println("\n" + prefix + "Applied devices roles for strategies:");
+ pw.println("\n" + prefix + "Applied devices roles for strategies (from API):");
mAppliedStrategyRoles.forEach((key, devices) -> {
pw.println(" " + prefix + "strategy: " + key.first
+ " role:" + key.second + " devices:" + devices); });
- pw.println("\n" + prefix + "Applied devices roles for presets:");
+ pw.println("\n" + prefix + "Applied devices roles for strategies (internal):");
+ mAppliedStrategyRolesInt.forEach((key, devices) -> {
+ pw.println(" " + prefix + "strategy: " + key.first
+ + " role:" + key.second + " devices:" + devices); });
+ pw.println("\n" + prefix + "Applied devices roles for presets (from API):");
mAppliedPresetRoles.forEach((key, devices) -> {
pw.println(" " + prefix + "preset: " + key.first
+ " role:" + key.second + " devices:" + devices); });
+ pw.println("\n" + prefix + "Applied devices roles for presets (internal:");
+ mAppliedPresetRolesInt.forEach((key, devices) -> {
+ pw.println(" " + prefix + "preset: " + key.first
+ + " role:" + key.second + " devices:" + devices); });
}
//------------------------------------------------------------
@@ -353,6 +362,9 @@
di.mDeviceCodecFormat);
}
mAppliedStrategyRoles.clear();
+ mAppliedStrategyRolesInt.clear();
+ mAppliedPresetRoles.clear();
+ mAppliedPresetRolesInt.clear();
applyConnectedDevicesRoles_l();
}
synchronized (mPreferredDevices) {
@@ -361,8 +373,8 @@
}
synchronized (mNonDefaultDevices) {
mNonDefaultDevices.forEach((strategy, devices) -> {
- addDevicesRoleForStrategy(
- strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); });
+ addDevicesRoleForStrategy(strategy, AudioSystem.DEVICE_ROLE_DISABLED,
+ devices, false /* internal */); });
}
synchronized (mPreferredDevicesForCapturePreset) {
// TODO: call audiosystem to restore
@@ -768,7 +780,7 @@
}
return status;
}
-
+ // Only used for external requests coming from an API
/*package*/ int setPreferredDevicesForStrategy(int strategy,
@NonNull List<AudioDeviceAttributes> devices) {
int status = AudioSystem.ERROR;
@@ -777,10 +789,17 @@
"setPreferredDevicesForStrategy, strategy: " + strategy
+ " devices: " + devices)).printLog(TAG));
status = setDevicesRoleForStrategy(
- strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+ strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, false /* internal */);
}
return status;
}
+ // Only used for internal requests
+ /*package*/ int setPreferredDevicesForStrategyInt(int strategy,
+ @NonNull List<AudioDeviceAttributes> devices) {
+
+ return setDevicesRoleForStrategy(
+ strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices, true /* internal */);
+ }
/*package*/ int removePreferredDevicesForStrategyAndSave(int strategy) {
final int status = removePreferredDevicesForStrategy(strategy);
@@ -789,7 +808,7 @@
}
return status;
}
-
+ // Only used for external requests coming from an API
/*package*/ int removePreferredDevicesForStrategy(int strategy) {
int status = AudioSystem.ERROR;
@@ -799,10 +818,15 @@
+ strategy)).printLog(TAG));
status = clearDevicesRoleForStrategy(
- strategy, AudioSystem.DEVICE_ROLE_PREFERRED);
+ strategy, AudioSystem.DEVICE_ROLE_PREFERRED, false /*internal */);
}
return status;
}
+ // Only used for internal requests
+ /*package*/ int removePreferredDevicesForStrategInt(int strategy) {
+ return clearDevicesRoleForStrategy(
+ strategy, AudioSystem.DEVICE_ROLE_PREFERRED, true /*internal */);
+ }
/*package*/ int setDeviceAsNonDefaultForStrategyAndSave(int strategy,
@NonNull AudioDeviceAttributes device) {
@@ -816,7 +840,7 @@
"setDeviceAsNonDefaultForStrategyAndSave, strategy: " + strategy
+ " device: " + device)).printLog(TAG));
status = addDevicesRoleForStrategy(
- strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
+ strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
}
if (status == AudioSystem.SUCCESS) {
@@ -838,7 +862,7 @@
+ strategy + " devices: " + device)).printLog(TAG));
status = removeDevicesRoleForStrategy(
- strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices);
+ strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices, false /* internal */);
}
if (status == AudioSystem.SUCCESS) {
@@ -877,6 +901,7 @@
return status;
}
+ // Only used for external requests coming from an API
private int setPreferredDevicesForCapturePreset(
int capturePreset, @NonNull List<AudioDeviceAttributes> devices) {
int status = AudioSystem.ERROR;
@@ -895,6 +920,7 @@
return status;
}
+ // Only used for external requests coming from an API
private int clearPreferredDevicesForCapturePreset(int capturePreset) {
int status = AudioSystem.ERROR;
@@ -905,20 +931,23 @@
return status;
}
- private int addDevicesRoleForCapturePreset(int capturePreset, int role,
+ // Only used for internal requests
+ private int addDevicesRoleForCapturePresetInt(int capturePreset, int role,
@NonNull List<AudioDeviceAttributes> devices) {
- return addDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+ return addDevicesRole(mAppliedPresetRolesInt, (p, r, d) -> {
return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d);
}, capturePreset, role, devices);
}
- private int removeDevicesRoleForCapturePreset(int capturePreset, int role,
+ // Only used for internal requests
+ private int removeDevicesRoleForCapturePresetInt(int capturePreset, int role,
@NonNull List<AudioDeviceAttributes> devices) {
- return removeDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
+ return removeDevicesRole(mAppliedPresetRolesInt, (p, r, d) -> {
return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d);
}, capturePreset, role, devices);
}
+ // Only used for external requests coming from an API
private int setDevicesRoleForCapturePreset(int capturePreset, int role,
@NonNull List<AudioDeviceAttributes> devices) {
return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
@@ -928,6 +957,7 @@
}, capturePreset, role, devices);
}
+ // Only used for external requests coming from an API
private int clearDevicesRoleForCapturePreset(int capturePreset, int role) {
return clearDevicesRole(mAppliedPresetRoles, (p, r, d) -> {
return mAudioSystem.clearDevicesRoleForCapturePreset(p, r);
@@ -945,32 +975,39 @@
}
private int addDevicesRoleForStrategy(int strategy, int role,
- @NonNull List<AudioDeviceAttributes> devices) {
- return addDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
- return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
- }, strategy, role, devices);
+ @NonNull List<AudioDeviceAttributes> devices,
+ boolean internal) {
+ return addDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
+ (s, r, d) -> {
+ return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+ }, strategy, role, devices);
}
private int removeDevicesRoleForStrategy(int strategy, int role,
- @NonNull List<AudioDeviceAttributes> devices) {
- return removeDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
- return mAudioSystem.removeDevicesRoleForStrategy(s, r, d);
- }, strategy, role, devices);
+ @NonNull List<AudioDeviceAttributes> devices,
+ boolean internal) {
+ return removeDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
+ (s, r, d) -> {
+ return mAudioSystem.removeDevicesRoleForStrategy(s, r, d);
+ }, strategy, role, devices);
}
private int setDevicesRoleForStrategy(int strategy, int role,
- @NonNull List<AudioDeviceAttributes> devices) {
- return setDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
- return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
- }, (s, r, d) -> {
- return mAudioSystem.clearDevicesRoleForStrategy(s, r);
- }, strategy, role, devices);
+ @NonNull List<AudioDeviceAttributes> devices,
+ boolean internal) {
+ return setDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
+ (s, r, d) -> {
+ return mAudioSystem.setDevicesRoleForStrategy(s, r, d);
+ }, (s, r, d) -> {
+ return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+ }, strategy, role, devices);
}
- private int clearDevicesRoleForStrategy(int strategy, int role) {
- return clearDevicesRole(mAppliedStrategyRoles, (s, r, d) -> {
- return mAudioSystem.clearDevicesRoleForStrategy(s, r);
- }, strategy, role);
+ private int clearDevicesRoleForStrategy(int strategy, int role, boolean internal) {
+ return clearDevicesRole(internal ? mAppliedStrategyRolesInt : mAppliedStrategyRoles,
+ (s, r, d) -> {
+ return mAudioSystem.clearDevicesRoleForStrategy(s, r);
+ }, strategy, role);
}
//------------------------------------------------------------
@@ -978,16 +1015,25 @@
// same list of devices for a given role and strategy and the corresponding systematic
// redundant work in audio policy manager and audio flinger.
// The key is the pair <Strategy , Role> and the value is the current list of devices.
-
+ // mAppliedStrategyRoles is for requests coming from an API.
+ // mAppliedStrategyRolesInt is for internal requests. Entries are removed when the requested
+ // device is disconnected.
private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
mAppliedStrategyRoles = new ArrayMap<>();
+ private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+ mAppliedStrategyRolesInt = new ArrayMap<>();
// Cache for applied roles for capture presets and devices. The cache avoids reapplying the
// same list of devices for a given role and capture preset and the corresponding systematic
// redundant work in audio policy manager and audio flinger.
// The key is the pair <Preset , Role> and the value is the current list of devices.
+ // mAppliedPresetRoles is for requests coming from an API.
+ // mAppliedPresetRolesInt is for internal requests. Entries are removed when the requested
+ // device is disconnected.
private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
mAppliedPresetRoles = new ArrayMap<>();
+ private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>>
+ mAppliedPresetRolesInt = new ArrayMap<>();
interface AudioSystemInterface {
int deviceRoleAction(int usecase, int role, @Nullable List<AudioDeviceAttributes> devices);
@@ -1113,9 +1159,9 @@
@GuardedBy("mDevicesLock")
private void purgeDevicesRoles_l() {
- purgeRoles(mAppliedStrategyRoles, (s, r, d) -> {
+ purgeRoles(mAppliedStrategyRolesInt, (s, r, d) -> {
return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
- purgeRoles(mAppliedPresetRoles, (p, r, d) -> {
+ purgeRoles(mAppliedPresetRolesInt, (p, r, d) -> {
return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
}
@@ -1124,8 +1170,12 @@
ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap,
AudioSystemInterface asi) {
synchronized (rolesMap) {
+ AudioDeviceInfo[] connectedDevices = AudioManager.getDevicesStatic(
+ AudioManager.GET_DEVICES_ALL);
+
Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole =
rolesMap.entrySet().iterator();
+
while (itRole.hasNext()) {
Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry =
itRole.next();
@@ -1133,9 +1183,15 @@
Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator();
while (itDev.hasNext()) {
AudioDeviceAttributes ada = itDev.next();
- final String devKey = DeviceInfo.makeDeviceListKey(ada.getInternalType(),
- ada.getAddress());
- if (mConnectedDevices.get(devKey) == null) {
+
+ AudioDeviceInfo device = Stream.of(connectedDevices)
+ .filter(d -> d.getInternalType() == ada.getInternalType())
+ .filter(d -> (!AudioSystem.isBluetoothDevice(d.getInternalType())
+ || (d.getAddress() == ada.getAddress())))
+ .findFirst()
+ .orElse(null);
+
+ if (device == null) {
asi.deviceRoleAction(keyRole.first, keyRole.second, Arrays.asList(ada));
itDev.remove();
}
@@ -1582,10 +1638,12 @@
}
if (disable) {
addDevicesRoleForStrategy(strategy.getId(),
- AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+ AudioSystem.DEVICE_ROLE_DISABLED,
+ Arrays.asList(ada), true /* internal */);
} else {
removeDevicesRoleForStrategy(strategy.getId(),
- AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+ AudioSystem.DEVICE_ROLE_DISABLED,
+ Arrays.asList(ada), true /* internal */);
}
}
}
@@ -1602,10 +1660,10 @@
+ ", disable: " + disable);
}
if (disable) {
- addDevicesRoleForCapturePreset(capturePreset,
+ addDevicesRoleForCapturePresetInt(capturePreset,
AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
} else {
- removeDevicesRoleForCapturePreset(capturePreset,
+ removeDevicesRoleForCapturePresetInt(capturePreset,
AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
}
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index d4bb445..e85eee81 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -309,6 +309,8 @@
*/
@VisibleForTesting
static final int DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC = 60;
+
+ private static final int PREFERRED_IKE_PROTOCOL_UNKNOWN = -1;
/**
* Prefer using {@link IkeSessionParams.ESP_IP_VERSION_AUTO} and
* {@link IkeSessionParams.ESP_ENCAP_TYPE_AUTO} for ESP packets.
@@ -3643,11 +3645,11 @@
final int natKeepalive =
carrierConfig.getInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT);
- final int preferredIpPortocol =
- carrierConfig.getInt(KEY_PREFERRED_IKE_PROTOCOL_INT);
+ final int preferredIpProtocol = carrierConfig.getInt(
+ KEY_PREFERRED_IKE_PROTOCOL_INT, PREFERRED_IKE_PROTOCOL_UNKNOWN);
final String mccMnc = perSubTm.getSimOperator(subId);
final CarrierConfigInfo info =
- buildCarrierConfigInfo(mccMnc, natKeepalive, preferredIpPortocol);
+ buildCarrierConfigInfo(mccMnc, natKeepalive, preferredIpProtocol);
synchronized (Vpn.this) {
mCachedCarrierConfigInfoPerSubId.put(subId, info);
}
@@ -5010,7 +5012,7 @@
* Retrieve the VpnProfileState for the profile provisioned by the given package.
*
* @return the VpnProfileState with current information, or null if there was no profile
- * provisioned by the given package.
+ * provisioned and started by the given package.
*/
@Nullable
public synchronized VpnProfileState getProvisionedVpnProfileState(
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index c7c0fab..7701bc6 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -701,11 +701,15 @@
maxDisplayMode == null ? mInfo.width : maxDisplayMode.getPhysicalWidth();
final int maxHeight =
maxDisplayMode == null ? mInfo.height : maxDisplayMode.getPhysicalHeight();
- mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
- mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
- mInfo.roundedCorners = RoundedCorners.fromResources(
- res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ // We cannot determine cutouts and rounded corners of external displays.
+ if (mStaticDisplayInfo.isInternal) {
+ mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+ mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ mInfo.roundedCorners = RoundedCorners.fromResources(
+ res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ }
+
mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
mInfo.displayShape = DisplayShape.fromResources(
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 2c54e1c..3eab4b0 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1579,6 +1579,10 @@
// If the device is not TV, we can't convert path to port-id, so stop here.
return true;
}
+ // Invalidate the physical address if parameters length is too short.
+ if (params.length < offset + 2) {
+ return false;
+ }
int path = HdmiUtils.twoBytesToInt(params, offset);
if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == getPhysicalAddress()) {
return true;
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1059c19..309a99e 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1644,6 +1644,26 @@
}
/**
+ * Notifies the corresponding router that it was successfully registered.
+ *
+ * <p>The message sent to the router includes a snapshot of the initial state, including
+ * known routes and the system {@link RoutingSessionInfo}.
+ *
+ * @param currentRoutes All currently known routes, which are filtered according to package
+ * visibility before being sent to the router.
+ * @param currentSystemSessionInfo The current system {@link RoutingSessionInfo}.
+ */
+ public void notifyRegistered(
+ List<MediaRoute2Info> currentRoutes, RoutingSessionInfo currentSystemSessionInfo) {
+ try {
+ mRouter.notifyRouterRegistered(
+ getVisibleRoutes(currentRoutes), currentSystemSessionInfo);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex);
+ }
+ }
+
+ /**
* Sends the corresponding router an {@link
* android.media.MediaRouter2.RouteCallback#onRoutesUpdated update} for the given {@code
* routes}.
@@ -2544,12 +2564,7 @@
return;
}
- try {
- routerRecord.mRouter.notifyRouterRegistered(
- currentRoutes, currentSystemSessionInfo);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex);
- }
+ routerRecord.notifyRegistered(currentRoutes, currentSystemSessionInfo);
}
private static void notifyRoutesUpdatedToRouterRecords(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 0171c20..3c97672 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2367,6 +2367,10 @@
final ActivityRecord prevTopActivity = mTopResumedActivity;
final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
+ if (topRootTask == null) {
+ // There's no focused task and there won't have any resumed activity either.
+ scheduleTopResumedActivityStateLossIfNeeded();
+ }
if (mService.isSleepingLocked()) {
// There won't be a next resumed activity. The top process should still be updated
// according to the current top focused activity.
@@ -2376,16 +2380,7 @@
}
// Ask previous activity to release the top state.
- final boolean prevActivityReceivedTopState =
- prevTopActivity != null && !mTopResumedActivityWaitingForPrev;
- // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
- // before the prevTopActivity one hasn't reported back yet. So server never sent the top
- // resumed state change message to prevTopActivity.
- if (prevActivityReceivedTopState
- && prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
- scheduleTopResumedStateLossTimeout(prevTopActivity);
- mTopResumedActivityWaitingForPrev = true;
- }
+ scheduleTopResumedActivityStateLossIfNeeded();
// Update the current top activity.
mTopResumedActivity = topRootTask.getTopResumedActivity();
@@ -2410,6 +2405,23 @@
mService.updateTopApp(mTopResumedActivity);
}
+ /** Schedule current top resumed activity state loss */
+ private void scheduleTopResumedActivityStateLossIfNeeded() {
+ if (mTopResumedActivity == null) {
+ return;
+ }
+
+ // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
+ // before the prevTopActivity one hasn't reported back yet. So server never sent the top
+ // resumed state change message to prevTopActivity.
+ if (!mTopResumedActivityWaitingForPrev
+ && mTopResumedActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
+ scheduleTopResumedStateLossTimeout(mTopResumedActivity);
+ mTopResumedActivityWaitingForPrev = true;
+ mTopResumedActivity = null;
+ }
+ }
+
/** Schedule top resumed state change if previous top activity already reported back. */
private void scheduleTopResumedActivityStateIfNeeded() {
if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 2df601f..b9f6e17 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -68,7 +68,7 @@
void notifyAppUnresponsive(InputApplicationHandle applicationHandle,
TimeoutRecord timeoutRecord) {
try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyAppUnresponsive()");
+ timeoutRecord.mLatencyTracker.notifyAppUnresponsiveStarted();
timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowStarted();
preDumpIfLockTooSlow();
timeoutRecord.mLatencyTracker.preDumpIfLockTooSlowEnded();
@@ -111,7 +111,6 @@
if (!blamePendingFocusRequest) {
Slog.i(TAG_WM, "ANR in " + activity.getName() + ". Reason: "
+ timeoutRecord.mReason);
- dumpAnrStateAsync(activity, null /* windowState */, timeoutRecord.mReason);
mUnresponsiveAppByDisplay.put(activity.getDisplayId(), activity);
}
}
@@ -123,8 +122,13 @@
} else {
activity.inputDispatchingTimedOut(timeoutRecord, INVALID_PID);
}
+
+ if (!blamePendingFocusRequest) {
+ dumpAnrStateAsync(activity, null /* windowState */, timeoutRecord.mReason);
+ }
+
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ timeoutRecord.mLatencyTracker.notifyAppUnresponsiveEnded();
}
}
@@ -139,7 +143,7 @@
void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
@NonNull TimeoutRecord timeoutRecord) {
try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "notifyWindowUnresponsive()");
+ timeoutRecord.mLatencyTracker.notifyWindowUnresponsiveStarted();
if (notifyWindowUnresponsive(token, timeoutRecord)) {
return;
}
@@ -150,7 +154,7 @@
}
notifyWindowUnresponsive(pid.getAsInt(), timeoutRecord);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ timeoutRecord.mLatencyTracker.notifyWindowUnresponsiveEnded();
}
}
@@ -168,6 +172,7 @@
final int pid;
final boolean aboveSystem;
final ActivityRecord activity;
+ final WindowState windowState;
timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted();
synchronized (mService.mGlobalLock) {
timeoutRecord.mLatencyTracker.waitingOnGlobalLockEnded();
@@ -175,7 +180,7 @@
if (target == null) {
return false;
}
- WindowState windowState = target.getWindowState();
+ windowState = target.getWindowState();
pid = target.getPid();
// Blame the activity if the input token belongs to the window. If the target is
// embedded, then we will blame the pid instead.
@@ -183,13 +188,13 @@
? windowState.mActivityRecord : null;
Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + timeoutRecord.mReason);
aboveSystem = isWindowAboveSystem(windowState);
- dumpAnrStateAsync(activity, windowState, timeoutRecord.mReason);
}
if (activity != null) {
activity.inputDispatchingTimedOut(timeoutRecord, pid);
} else {
mService.mAmInternal.inputDispatchingTimedOut(pid, aboveSystem, timeoutRecord);
}
+ dumpAnrStateAsync(activity, windowState, timeoutRecord.mReason);
return true;
}
@@ -199,15 +204,10 @@
private void notifyWindowUnresponsive(int pid, TimeoutRecord timeoutRecord) {
Slog.i(TAG_WM,
"ANR in input window owned by pid=" + pid + ". Reason: " + timeoutRecord.mReason);
- timeoutRecord.mLatencyTracker.waitingOnGlobalLockStarted();
- synchronized (mService.mGlobalLock) {
- timeoutRecord.mLatencyTracker.waitingOnGlobalLockEnded();
- dumpAnrStateAsync(null /* activity */, null /* windowState */, timeoutRecord.mReason);
- }
-
// We cannot determine the z-order of the window, so place the anr dialog as high
// as possible.
mService.mAmInternal.inputDispatchingTimedOut(pid, true /*aboveSystem*/, timeoutRecord);
+ dumpAnrStateAsync(null /* activity */, null /* windowState */, timeoutRecord.mReason);
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 7f845e6..21004ab 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -272,15 +272,17 @@
/** @return A new source computed by the specified window frame in the given display frames. */
InsetsSource createSimulatedSource(DisplayFrames displayFrames, Rect frame) {
- // Don't copy visible frame because it might not be calculated in the provided display
- // frames and it is not significant for this usage.
- final InsetsSource source = new InsetsSource(mSource.getId(), mSource.getType());
- source.setVisible(mSource.isVisible());
+ final InsetsSource source = new InsetsSource(mSource);
mTmpRect.set(frame);
if (mFrameProvider != null) {
mFrameProvider.apply(displayFrames, mWindowContainer, mTmpRect);
}
source.setFrame(mTmpRect);
+
+ // Don't copy visible frame because it might not be calculated in the provided display
+ // frames and it is not significant for this usage.
+ source.setVisibleFrame(null);
+
return source;
}
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index afef85e..58e1c54 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -314,6 +314,11 @@
}
final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+ if (swBitmap == null) {
+ Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + ", isMutable="
+ + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed.");
+ return false;
+ }
final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
try {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ce4f445..1ae1e03 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1847,6 +1847,9 @@
td.setEnsureStatusBarContrastWhenTransparent(
atd.getEnsureStatusBarContrastWhenTransparent());
}
+ if (td.getStatusBarAppearance() == 0) {
+ td.setStatusBarAppearance(atd.getStatusBarAppearance());
+ }
if (td.getNavigationBarColor() == 0) {
td.setNavigationBarColor(atd.getNavigationBarColor());
td.setEnsureNavigationBarContrastWhenTransparent(
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cb6a57b..7460d12 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8270,6 +8270,13 @@
@Override
public void addTrustedTaskOverlay(int taskId,
SurfaceControlViewHost.SurfacePackage overlay) {
+ if (overlay == null) {
+ throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+ } else if (overlay.getSurfaceControl() == null
+ || !overlay.getSurfaceControl().isValid()) {
+ throw new IllegalArgumentException(
+ "Invalid overlay surfacecontrol passed in for task=" + taskId);
+ }
synchronized (mGlobalLock) {
final Task task = mRoot.getRootTask(taskId);
if (task == null) {
@@ -8282,6 +8289,13 @@
@Override
public void removeTrustedTaskOverlay(int taskId,
SurfaceControlViewHost.SurfacePackage overlay) {
+ if (overlay == null) {
+ throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+ } else if (overlay.getSurfaceControl() == null
+ || !overlay.getSurfaceControl().isValid()) {
+ throw new IllegalArgumentException(
+ "Invalid overlay surfacecontrol passed in for task=" + taskId);
+ }
synchronized (mGlobalLock) {
final Task task = mRoot.getRootTask(taskId);
if (task == null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7cd7f69..8f49384 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3883,13 +3883,15 @@
}
/**
- * @return true if activity bounds are letterboxed or letterboxed for diplay cutout.
+ * @return {@code true} if activity bounds are letterboxed or letterboxed for display cutout.
+ * Note that it's always {@code false} if the activity is in pip mode.
*
* <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
* LetterboxUiController#shouldShowLetterboxUi} for more context.
*/
boolean areAppWindowBoundsLetterboxed() {
return mActivityRecord != null
+ && !mActivityRecord.inPinnedWindowingMode()
&& (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index edb8e0c..b43209f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -49,6 +49,7 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MICROPHONE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MTE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY;
@@ -73,7 +74,6 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USERS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
@@ -139,6 +139,7 @@
import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
@@ -13720,7 +13721,7 @@
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
+ UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
@@ -13810,13 +13811,13 @@
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
+ UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS});
+ UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER});
USER_RESTRICTION_PERMISSIONS.put(
@@ -13842,7 +13843,7 @@
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS});
+ UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
@@ -14836,17 +14837,23 @@
"Caller is not managed profile owner or device owner;"
+ " only managed profile owner or device owner may control the preferential"
+ " network service");
- synchronized (getLockObject()) {
- final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
- caller.getUserId());
- if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
- preferentialNetworkServiceConfigs)) {
- requiredAdmin.mPreferentialNetworkServiceConfigs =
- new ArrayList<>(preferentialNetworkServiceConfigs);
- saveSettingsLocked(caller.getUserId());
+
+ try {
+ updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
+ synchronized (getLockObject()) {
+ final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked(
+ caller.getUserId());
+ if (!requiredAdmin.mPreferentialNetworkServiceConfigs.equals(
+ preferentialNetworkServiceConfigs)) {
+ requiredAdmin.mPreferentialNetworkServiceConfigs =
+ new ArrayList<>(preferentialNetworkServiceConfigs);
+ saveSettingsLocked(caller.getUserId());
+ }
}
+ } catch (Exception e) {
+ Slogf.e(LOG_TAG, "Failed to set preferential network service configs");
+ throw e;
}
- updateNetworkPreferenceForUser(caller.getUserId(), preferentialNetworkServiceConfigs);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PREFERENTIAL_NETWORK_SERVICE_ENABLED)
.setBoolean(preferentialNetworkServiceConfigs
@@ -15139,7 +15146,7 @@
private void enforceCanSetLockTaskFeaturesOnFinancedDevice(CallerIdentity caller, int flags) {
int allowedFlags = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
| LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
- | LOCK_TASK_FEATURE_NOTIFICATIONS;
+ | LOCK_TASK_FEATURE_NOTIFICATIONS | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
if (!isFinancedDeviceOwner(caller)) {
return;
@@ -15150,7 +15157,8 @@
"Permitted lock task features when managing a financed device: "
+ "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, "
+ "LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, "
- + "or LOCK_TASK_FEATURE_NOTIFICATIONS");
+ + "LOCK_TASK_FEATURE_NOTIFICATIONS"
+ + " or LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK");
}
}
@@ -23019,6 +23027,7 @@
MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_MICROPHONE,
MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+ MANAGE_DEVICE_POLICY_MODIFY_USERS,
MANAGE_DEVICE_POLICY_MTE,
MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
@@ -23042,7 +23051,6 @@
MANAGE_DEVICE_POLICY_TIME,
MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
- MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_VPN,
MANAGE_DEVICE_POLICY_WALLPAPER,
MANAGE_DEVICE_POLICY_WIFI,
@@ -23063,12 +23071,12 @@
MANAGE_DEVICE_POLICY_KEYGUARD,
MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
MANAGE_DEVICE_POLICY_LOCK_TASK,
+ MANAGE_DEVICE_POLICY_MODIFY_USERS,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
MANAGE_DEVICE_POLICY_SAFE_BOOT,
MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
MANAGE_DEVICE_POLICY_TIME,
- MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_WIPE_DATA
);
@@ -23154,6 +23162,7 @@
MANAGE_DEVICE_POLICY_FUN,
MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+ MANAGE_DEVICE_POLICY_MODIFY_USERS,
MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
MANAGE_DEVICE_POLICY_PRINTING,
MANAGE_DEVICE_POLICY_PROFILES,
@@ -23162,7 +23171,6 @@
MANAGE_DEVICE_POLICY_SMS,
MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
- MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_WINDOWS,
SET_TIME,
SET_TIME_ZONE
@@ -23352,6 +23360,8 @@
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MODIFY_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
@@ -23372,8 +23382,6 @@
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USERS,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_VPN,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WALLPAPER,
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 6bce71e..948687a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1406,7 +1406,8 @@
eq(getUidForPackage(PACKAGE_GREEN)), anyInt(), eq(Intent.ACTION_TIME_TICK),
eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
- anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class), anyString()),
+ anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class),
+ anyString(), anyInt(), anyInt()),
times(1));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b7dbaf9..f89f73c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -37,6 +37,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -1009,6 +1010,72 @@
0.001f);
}
+ @Test
+ public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = true;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNotNull();
+ assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+ assertThat(info.roundedCorners).isNotNull();
+ assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+ }
+
+ @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = false;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNull();
+ assertThat(info.roundedCorners).isNull();
+ }
+
+ private void setupCutoutAndRoundedCorners() {
+ String sampleCutout = "M 507,66\n"
+ + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+ + "Z\n"
+ + "@left\n";
+ // Setup some default cutout
+ when(mMockedResources.getString(
+ com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+ .thenReturn(sampleCutout);
+ when(mMockedResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+ }
+
private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
float expectedXdpi,
float expectedYDpi,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 8cfc150..7e638a8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -280,6 +280,7 @@
@SmallTest
@Test
public void testRegisterProxy() throws Exception {
+ when(mProxyManager.displayBelongsToCaller(anyInt(), anyInt())).thenReturn(true);
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY), anyInt(),
eq(mMockSecurityPolicy),
@@ -289,9 +290,9 @@
@SmallTest
@Test
- public void testRegisterProxyWithoutA11yPermission() throws Exception {
+ public void testRegisterProxyWithoutA11yPermissionOrRole() throws Exception {
doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+ .checkForAccessibilityPermissionOrRole();
assertThrows(SecurityException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
@@ -314,7 +315,7 @@
@SmallTest
@Test
public void testRegisterProxyForDefaultDisplay() throws Exception {
- assertThrows(IllegalArgumentException.class,
+ assertThrows(SecurityException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.DEFAULT_DISPLAY));
verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
any(), any(), any());
@@ -332,6 +333,7 @@
@SmallTest
@Test
public void testUnRegisterProxyWithPermission() throws Exception {
+ when(mProxyManager.displayBelongsToCaller(anyInt(), anyInt())).thenReturn(true);
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
mA11yms.unregisterProxyForDisplay(TEST_DISPLAY);
@@ -340,9 +342,9 @@
@SmallTest
@Test
- public void testUnRegisterProxyWithoutA11yPermission() {
+ public void testUnRegisterProxyWithoutA11yPermissionOrRole() {
doThrow(SecurityException.class).when(mMockSecurityPolicy)
- .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
+ .checkForAccessibilityPermissionOrRole();
assertThrows(SecurityException.class,
() -> mA11yms.unregisterProxyForDisplay(TEST_DISPLAY));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index b0fd649..c98de7c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -66,6 +66,7 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -307,6 +308,7 @@
MagnificationScaleProvider.MAX_SCALE);
}
+ @Ignore("b/278816260: We could refer to b/182561174#comment4 for solution.")
@Test
public void logTrackingTypingFocus_processScroll_logDuration() {
WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager);
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
index 8a107ad..dccc26a 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncDataTest.java
@@ -18,7 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
-import android.os.Parcel;
+import android.os.Bundle;
import android.testing.AndroidTestingRunner;
import org.junit.Test;
@@ -28,7 +28,7 @@
public class CallMetadataSyncDataTest {
@Test
- public void call_writeToParcel_fromParcel_reconstructsSuccessfully() {
+ public void call_writeToBundle_fromBundle_reconstructsSuccessfully() {
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
final String id = "5";
final String callerId = "callerId";
@@ -36,6 +36,7 @@
final String appName = "appName";
final String appIdentifier = "com.google.test";
final int status = 1;
+ final int direction = android.companion.Telecom.Call.OUTGOING;
final int control1 = 2;
final int control2 = 3;
call.setId(id);
@@ -45,14 +46,13 @@
new CallMetadataSyncData.CallFacilitator(appName, appIdentifier);
call.setFacilitator(callFacilitator);
call.setStatus(status);
+ call.setDirection(direction);
call.addControl(control1);
call.addControl(control2);
- Parcel parcel = Parcel.obtain();
- call.writeToParcel(parcel, /* flags= */ 0);
- parcel.setDataPosition(0);
- final CallMetadataSyncData.Call reconstructedCall = CallMetadataSyncData.Call.fromParcel(
- parcel);
+ final Bundle bundle = call.writeToBundle();
+ final CallMetadataSyncData.Call reconstructedCall = CallMetadataSyncData.Call.fromBundle(
+ bundle);
assertThat(reconstructedCall.getId()).isEqualTo(id);
assertThat(reconstructedCall.getCallerId()).isEqualTo(callerId);
@@ -60,6 +60,7 @@
assertThat(reconstructedCall.getFacilitator().getName()).isEqualTo(appName);
assertThat(reconstructedCall.getFacilitator().getIdentifier()).isEqualTo(appIdentifier);
assertThat(reconstructedCall.getStatus()).isEqualTo(status);
+ assertThat(reconstructedCall.getDirection()).isEqualTo(direction);
assertThat(reconstructedCall.getControls()).containsExactly(control1, control2);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
index 6a939ab..201d8f9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
@@ -149,6 +149,27 @@
}
@Test
+ public void updateCallDetails_transitionDialingToOngoing() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext(),
+ mUninitializedCallDetails, /* callAudioState= */ null);
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_DIALING,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ assertWithMessage("Wrong status for dialing state").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.DIALING);
+ assertWithMessage("Wrong controls for dialing state").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.END));
+ crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
+ Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
+ assertWithMessage("Wrong status for active state").that(crossDeviceCall.getStatus())
+ .isEqualTo(android.companion.Telecom.Call.ONGOING);
+ assertWithMessage("Wrong controls for active state").that(crossDeviceCall.getControls())
+ .isEqualTo(Set.of(android.companion.Telecom.END,
+ android.companion.Telecom.MUTE,
+ android.companion.Telecom.PUT_ON_HOLD));
+ }
+
+ @Test
public void updateSilencedIfRinging_ringing_silenced() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
InstrumentationRegistry.getTargetContext(),
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
index 7688fcb..7e392a4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
@@ -162,13 +162,31 @@
}
@Test
- public void createPhoneAccount_success() {
+ public void createPhoneAccount_sip_success() {
final PhoneAccount phoneAccount =
CrossDeviceSyncController.PhoneAccountManager.createPhoneAccount(
new PhoneAccountHandle(
new ComponentName("com.google.test", "com.google.test.Activity"),
- "id"), "Test App", "com.google.test");
+ "id"), "Test App", "com.google.test", 1, false);
assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
+ assertWithMessage("Wrong schemes supported; should not support TEL")
+ .that(phoneAccount.supportsUriScheme(PhoneAccount.SCHEME_TEL)).isFalse();
+ assertWithMessage("Wrong schemes supported; should support SIP")
+ .that(phoneAccount.supportsUriScheme(PhoneAccount.SCHEME_SIP)).isTrue();
+ }
+
+ @Test
+ public void createPhoneAccount_tel_success() {
+ final PhoneAccount phoneAccount =
+ CrossDeviceSyncController.PhoneAccountManager.createPhoneAccount(
+ new PhoneAccountHandle(
+ new ComponentName("com.google.test", "com.google.test.Activity"),
+ "id"), "Test App", "com.google.test", 1, true);
+ assertWithMessage("Could not create phone account").that(phoneAccount).isNotNull();
+ assertWithMessage("Wrong schemes supported; should support TEL")
+ .that(phoneAccount.supportsUriScheme(PhoneAccount.SCHEME_TEL)).isTrue();
+ assertWithMessage("Wrong schemes supported; should not support SIP")
+ .that(phoneAccount.supportsUriScheme(PhoneAccount.SCHEME_SIP)).isFalse();
}
@Test
@@ -214,9 +232,10 @@
}
@Test
- public void updateCalls_newCall() {
+ public void updateCalls_newIncomingCall_added() {
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
call.setId("123abc");
+ call.setDirection(android.companion.Telecom.Call.INCOMING);
call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
callMetadataSyncData.addCall(call);
@@ -225,25 +244,115 @@
new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
verify(mMockTelecomManager, times(1)).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
}
@Test
- public void updateCalls_newCall_noFacilitator() {
+ public void updateCalls_newOutgoingCall_added() {
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
call.setId("123abc");
+ call.setDirection(android.companion.Telecom.Call.OUTGOING);
+ call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+ call.setCallerId("555-555-5555");
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
callMetadataSyncData.addCall(call);
final CrossDeviceSyncController.CallManager callManager =
new CrossDeviceSyncController.CallManager(mMockContext,
new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
- verify(mMockTelecomManager, times(0)).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, times(1)).placeCall(any(), any());
}
@Test
- public void updateCalls_existingCall() {
+ public void updateCalls_newOutgoingCall_noAddress_notAdded() {
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
call.setId("123abc");
+ call.setDirection(android.companion.Telecom.Call.OUTGOING);
+ call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
+ }
+
+ @Test
+ public void updateCalls_newIncomingCall_noFacilitator_notAdded() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc");
+ call.setDirection(android.companion.Telecom.Call.INCOMING);
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
+ }
+
+ @Test
+ public void updateCalls_newOutgoingCall_noFacilitator_notAdded() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc");
+ call.setDirection(android.companion.Telecom.Call.OUTGOING);
+ call.setCallerId("555-555-5555");
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
+ }
+
+ @Test
+ public void updateCalls_newIncomingCall_isSelfOwned_notAdded() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc::originalId");
+ call.setDirection(android.companion.Telecom.Call.INCOMING);
+ call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.addSelfOwnedCallId("originalId");
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
+ }
+
+
+ @Test
+ public void updateCalls_newOutgoingCall_isSelfOwned_notAdded() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc::originalId");
+ call.setDirection(android.companion.Telecom.Call.OUTGOING);
+ call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+ call.setCallerId("555-555-5555");
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.addSelfOwnedCallId("originalId");
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
+ }
+
+ @Test
+ public void updateCalls_existingIncomingCall() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc");
+ call.setDirection(android.companion.Telecom.Call.INCOMING);
+ call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
callMetadataSyncData.addCall(call);
final CrossDeviceSyncController.CallManager callManager =
@@ -252,6 +361,25 @@
callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId()));
callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
+ }
+
+ @Test
+ public void updateCalls_existingOutgoingCall() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc");
+ call.setDirection(android.companion.Telecom.Call.OUTGOING);
+ call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+ call.setCallerId("555-555-5555");
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId()));
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
}
@Test
@@ -266,6 +394,7 @@
callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId(), "fakeCallId"));
callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, never()).placeCall(any(), any());
assertWithMessage("Hasn't removed the id of the removed call")
.that(callManager.mCallIds)
.containsExactly(/* associationId= */ 0, Set.of(call.getId()));
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index c872a11..0e92d22 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -18,8 +18,16 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -29,6 +37,7 @@
import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
import com.android.server.pm.UserManagerInternal;
import com.google.common.collect.ImmutableList;
@@ -56,12 +65,21 @@
private static final String PACKAGE_NAME = "com.test.package";
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName(PACKAGE_NAME, "TestClass");
+
private static final Context sContext = ApplicationProvider.getApplicationContext();
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private UserManagerInternal mMockUserManagerInternal;
+ @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+
+ private boolean mDevCfgEnableContentProtectionReceiver;
+
+ private int mContentProtectionBlocklistManagersCreated;
+
private ContentCaptureManagerService mContentCaptureManagerService;
@Before
@@ -69,46 +87,235 @@
when(mMockUserManagerInternal.getUserInfos()).thenReturn(new UserInfo[0]);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
- mContentCaptureManagerService = new ContentCaptureManagerService(sContext);
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
}
@Test
- public void getOptions_notAllowlisted() {
+ public void constructor_default_doesNotCreateContentProtectionBlocklistManager() {
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void constructor_flagDisabled_doesNotContentProtectionBlocklistManager() {
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void constructor_flagEnabled_createsContentProtectionBlocklistManager() {
+ mDevCfgEnableContentProtectionReceiver = true;
+
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+ verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+ }
+
+ @Test
+ public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
+ verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+
+ mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+ verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
ContentCaptureOptions actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
USER_ID, PACKAGE_NAME);
assertThat(actual).isNull();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
}
@Test
- public void getOptions_allowlisted_contentCaptureReceiver() {
- mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
- USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+ public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
ContentCaptureOptions actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
USER_ID, PACKAGE_NAME);
assertThat(actual).isNotNull();
- assertThat(actual.enableReceiver).isTrue();
- assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
- assertThat(actual.whitelistedComponents).isNull();
- }
-
- @Test
- public void getOptions_allowlisted_bothReceivers() {
- mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = true;
- mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
- USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
-
- ContentCaptureOptions actual =
- mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
- USER_ID, PACKAGE_NAME);
-
- assertThat(actual).isNotNull();
- assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.enableReceiver).isFalse();
+ assertThat(actual.contentProtectionOptions).isNotNull();
assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
assertThat(actual.whitelistedComponents).isNull();
}
+
+ @Test
+ public void getOptions_contentCaptureEnabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ ContentCaptureOptions actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isNotNull();
+ assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.contentProtectionOptions).isNotNull();
+ assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
+ assertThat(actual.whitelistedComponents).isNull();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ }
+
+ @Test
+ public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ ContentCaptureOptions actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isNotNull();
+ assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.contentProtectionOptions).isNotNull();
+ assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+ assertThat(actual.whitelistedComponents).isNull();
+ }
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ }
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionNotChecked() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionNotChecked() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, /* packageNames= */ null, ImmutableList.of(COMPONENT_NAME));
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isTrue();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isContentProtectionReceiverEnabled_withoutBlocklistManager() {
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ private class TestContentCaptureManagerService extends ContentCaptureManagerService {
+
+ TestContentCaptureManagerService() {
+ super(sContext);
+ this.mDevCfgEnableContentProtectionReceiver =
+ ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+ }
+
+ @Override
+ protected boolean getEnableContentProtectionReceiverLocked() {
+ return ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+ }
+
+ @Override
+ protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager(
+ @NonNull Context context) {
+ mContentProtectionBlocklistManagersCreated++;
+ return mMockContentProtectionBlocklistManager;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
new file mode 100644
index 0000000..24561c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 544200
+
+include /core/java/android/view/contentcapture/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
new file mode 100644
index 0000000..2f57fd3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureSession.FLUSH_REASON_LOGIN_DETECTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureDirectManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link RemoteContentProtectionService}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksServicesTests:com.android.server.contentprotection.RemoteContentProtectionServiceTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteContentProtectionServiceTest {
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
+
+ private RemoteContentProtectionService mRemoteContentProtectionService;
+
+ private int mConnectCallCount = 0;
+
+ @Before
+ public void setup() {
+ ComponentName componentName = new ComponentName(mContext.getPackageName(), "TestClass");
+ mRemoteContentProtectionService =
+ new TestRemoteContentProtectionService(mContext, componentName);
+ }
+
+ @Test
+ public void doesNotAutoConnect() {
+ assertThat(mConnectCallCount).isEqualTo(0);
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ }
+
+ @Test
+ public void getAutoDisconnectTimeoutMs() {
+ long actual = mRemoteContentProtectionService.getAutoDisconnectTimeoutMs();
+
+ assertThat(actual).isEqualTo(3000L);
+ }
+
+ @Test
+ public void onLoginDetected() throws Exception {
+ ContentCaptureEvent event =
+ new ContentCaptureEvent(/* sessionId= */ 1111, /* type= */ 2222);
+ ParceledListSlice<ContentCaptureEvent> events =
+ new ParceledListSlice<>(ImmutableList.of(event));
+
+ mRemoteContentProtectionService.onLoginDetected(events);
+
+ verify(mMockContentCaptureDirectManager)
+ .sendEvents(events, FLUSH_REASON_LOGIN_DETECTED, /* options= */ null);
+ }
+
+ private final class TestRemoteContentProtectionService extends RemoteContentProtectionService {
+
+ TestRemoteContentProtectionService(Context context, ComponentName componentName) {
+ super(context, componentName, UserHandle.myUserId(), /* bindAllowInstant= */ false);
+ }
+
+ @Override // from ServiceConnector
+ public synchronized AndroidFuture<IContentCaptureDirectManager> connect() {
+ mConnectCallCount++;
+ return AndroidFuture.completedFuture(mMockContentCaptureDirectManager);
+ }
+
+ @Override // from ServiceConnector
+ public boolean run(@NonNull ServiceConnector.VoidJob<IContentCaptureDirectManager> job) {
+ try {
+ job.run(mMockContentCaptureDirectManager);
+ } catch (Exception ex) {
+ fail("Unexpected exception: " + ex);
+ }
+ return true;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 39de2cf..99a3b80 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -7842,7 +7842,7 @@
throws Exception {
int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD
| LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS
- | LOCK_TASK_FEATURE_NOTIFICATIONS;
+ | LOCK_TASK_FEATURE_NOTIFICATIONS | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
setDeviceOwner();
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
@@ -7857,7 +7857,7 @@
public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException()
throws Exception {
int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW
- | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK;
+ | LOCK_TASK_FEATURE_HOME;
setDeviceOwner();
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
// Called during setup.
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
new file mode 100644
index 0000000..920c376
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+/**
+ * TV specific tests for {@link HdmiControlService} class.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class HdmiControlServiceTvTest {
+
+ private static final String TAG = "HdmiControlServiceTvTest";
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
+ private FakeNativeWrapper mNativeWrapper;
+ private HdmiEarcController mHdmiEarcController;
+ private FakeEarcNativeWrapper mEarcNativeWrapper;
+ private Looper mMyLooper;
+ private TestLooper mTestLooper = new TestLooper();
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ mMyLooper = mTestLooper.getLooper();
+
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
+ mHdmiControlService =
+ new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
+ @Override
+ int pathToPortId(int path) {
+ return Constants.INVALID_PORT_ID + 1;
+ }
+ };
+
+ mMyLooper = mTestLooper.getLooper();
+ mHdmiControlService.setIoLooper(mMyLooper);
+ mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+ mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mEarcNativeWrapper = new FakeEarcNativeWrapper();
+ mHdmiEarcController = HdmiEarcController.createWithNativeWrapper(
+ mHdmiControlService, mEarcNativeWrapper);
+ mHdmiControlService.setEarcController(mHdmiEarcController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(
+ mHdmiControlService));
+ mHdmiControlService.initService();
+
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void onCecMessage_shortPhysicalAddress_featureAbortInvalidOperand() {
+ // Invalid <Inactive Source> message.
+ HdmiCecMessage message = HdmiUtils.buildMessage("40:9D:14");
+
+ mNativeWrapper.onCecMessage(message);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, Constants.MESSAGE_INACTIVE_SOURCE,
+ Constants.ABORT_INVALID_OPERAND);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+ }
+
+ @Test
+ public void handleCecCommand_shortPhysicalAddress_returnsAbortInvalidOperand() {
+ // Invalid <Active Source> message.
+ HdmiCecMessage message = HdmiUtils.buildMessage("4F:82:10");
+
+ // In case of a broadcasted message <Feature Abort> is not expected.
+ // See CEC 1.4b specification, 12.2 Protocol General Rules for detail.
+ assertThat(mHdmiControlService.handleCecCommand(message))
+ .isEqualTo(Constants.ABORT_INVALID_OPERAND);
+ }
+
+ @Test
+ public void test_verifyPhysicalAddresses() {
+ // <Routing Change>
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10:00:40:00"))).isTrue();
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10:00:40"))).isFalse();
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10"))).isFalse();
+
+ // <System Audio Mode Request>
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("40:70:00:00"))).isTrue();
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("40:70:00"))).isFalse();
+
+ // <Active Source>
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("4F:82:10:00"))).isTrue();
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("4F:82:10"))).isFalse();
+ }
+}
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index e57c833..6dcfa6d 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -21,12 +21,12 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.telecom.IVideoProvider;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import com.android.internal.telecom.IVideoProvider;
-
/**
* A parcelable representation of a conference connection.
* @hide
@@ -287,6 +287,14 @@
return mCallDirection;
}
+ public String getCallerDisplayName() {
+ return mCallerDisplayName;
+ }
+
+ public int getCallerDisplayNamePresentation() {
+ return mCallerDisplayNamePresentation;
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<ParcelableConference> CREATOR =
new Parcelable.Creator<ParcelableConference> () {
@Override
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
index 2faecc2..5f0c8d72 100644
--- a/telecomm/java/android/telecom/StatusHints.java
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -16,14 +16,19 @@
package android.telecom;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
@@ -33,7 +38,7 @@
public final class StatusHints implements Parcelable {
private final CharSequence mLabel;
- private final Icon mIcon;
+ private Icon mIcon;
private final Bundle mExtras;
/**
@@ -48,11 +53,31 @@
public StatusHints(CharSequence label, Icon icon, Bundle extras) {
mLabel = label;
- mIcon = icon;
+ mIcon = validateAccountIconUserBoundary(icon, Binder.getCallingUserHandle());
mExtras = extras;
}
/**
+ * @param icon
+ * @hide
+ */
+ @VisibleForTesting
+ public StatusHints(@Nullable Icon icon) {
+ mLabel = null;
+ mExtras = null;
+ mIcon = icon;
+ }
+
+ /**
+ *
+ * @param icon
+ * @hide
+ */
+ public void setIcon(@Nullable Icon icon) {
+ mIcon = icon;
+ }
+
+ /**
* @return A package used to load the icon.
*
* @hide
@@ -112,6 +137,30 @@
return 0;
}
+ /**
+ * Validates the StatusHints image icon to see if it's not in the calling user space.
+ * Invalidates the icon if so, otherwise returns back the original icon.
+ *
+ * @param icon
+ * @return icon (validated)
+ * @hide
+ */
+ public static Icon validateAccountIconUserBoundary(Icon icon, UserHandle callingUserHandle) {
+ // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+ // incompatible types.
+ if (icon != null && (icon.getType() == Icon.TYPE_URI
+ || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+ String encodedUser = icon.getUri().getEncodedUserInfo();
+ // If there is no encoded user, the URI is calling into the calling user space
+ if (encodedUser != null) {
+ int userId = Integer.parseInt(encodedUser);
+ // Do not try to save the icon if the user id isn't in the calling user space.
+ if (userId != callingUserHandle.getIdentifier()) return null;
+ }
+ }
+ return icon;
+ }
+
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeCharSequence(mLabel);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
new file mode 100644
index 0000000..00316ea
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import android.tools.common.datatypes.Rect
+
+/**
+ * Test launching an activity with AlwaysExpand rule.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity.
+ * Transitions:
+ * A start C with alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
+ *
+ * To run this test: `atest FlickerTests:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: FlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ // Launch a split
+ testApp.launchViaIntent(wmHelper)
+ testApp.launchSecondaryActivity(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+ }
+ transitions {
+ // Launch C with alwaysExpand
+ testApp.launchAlwaysExpandActivity(wmHelper)
+ }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /** Transition begins with a split. */
+ @Presubmit
+ @Test
+ fun startsWithSplit() {
+ flicker.assertWmStart {
+ this.isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ flicker.assertWmStart {
+ this.isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+
+ /** Main activity should become invisible after being covered by always expand activity. */
+ @Presubmit
+ @Test
+ fun mainActivityLayerBecomesInvisible() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Secondary activity should become invisible after being covered by always expand activity. */
+ @Presubmit
+ @Test
+ fun secondaryActivityLayerBecomesInvisible() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** At the end of transition always expand activity is in fullscreen. */
+ @Presubmit
+ @Test
+ fun endsWithAlwaysExpandActivityCoveringFullScreen() {
+ flicker.assertWmEnd {
+ this.visibleRegion(ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /** Always expand activity is on top of the split. */
+ @Presubmit
+ @Test
+ fun endsWithAlwaysExpandActivityOnTop() {
+ flicker.assertWmEnd {
+ this.isAppWindowOnTop(
+ ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+ }
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
+
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
similarity index 100%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
similarity index 100%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index e019b2b..a21965e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -75,6 +75,29 @@
.StateSyncBuilder()
.withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
.waitForAndVerify()
+ }
+
+ /**
+ * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
+ * a fullscreen window on top of the visible region.
+ */
+ fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
+ val launchButton =
+ uiDevice.wait(
+ Until.findObject(
+ By.res(getPackage(),
+ "launch_always_expand_activity_button")),
+ FIND_TIMEOUT
+ )
+ require(launchButton != null) {
+ "Can't find launch always expand activity button on screen."
+ }
+ launchButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_PAUSED)
+ .waitForAndVerify()
}
/**
@@ -105,6 +128,9 @@
val SECONDARY_ACTIVITY_COMPONENT =
ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
+ val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
+
val PLACEHOLDER_PRIMARY_COMPONENT =
ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
.toFlickerComponent()
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 1ec9ec9..dc9ff3b 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -198,6 +198,13 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false"/>
<activity
+ android:name=".ActivityEmbeddingAlwaysExpandActivity"
+ android:label="ActivityEmbedding AlwaysExpand"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="false"/>
+ <activity
android:name=".ActivityEmbeddingPlaceholderPrimaryActivity"
android:label="ActivityEmbedding Placeholder Primary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index d78b9a8..f5241ca 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -37,4 +37,12 @@
android:onClick="launchPlaceholderSplit"
android:text="Launch Placeholder Split" />
+ <Button
+ android:id="@+id/launch_always_expand_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:onClick="launchAlwaysExpandActivity"
+ android:text="Launch Always Expand Activity" />
+
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
new file mode 100644
index 0000000..d9b24ed
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity with alwaysExpand=true (launched via R.id.launch_always_expand_activity_button)
+ */
+public class ActivityEmbeddingAlwaysExpandActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_embedding_base_layout);
+ findViewById(R.id.root_activity_layout).setBackgroundColor(Color.GREEN);
+ }
+
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 6a7a2cc..6120254 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -23,6 +23,9 @@
import android.util.Log;
import android.view.View;
+import androidx.window.embedding.ActivityFilter;
+import androidx.window.embedding.ActivityRule;
+import androidx.window.embedding.RuleController;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.EmbeddingRule;
import androidx.window.extensions.embedding.SplitPairRule;
@@ -30,6 +33,7 @@
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
+import java.util.HashSet;
import java.util.Set;
/** Main activity of the ActivityEmbedding test app to launch other embedding activities. */
@@ -50,6 +54,23 @@
ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
}
+ /** R.id.launch_always_expand_activity_button onClick */
+ public void launchAlwaysExpandActivity(View view) {
+ final Set<ActivityFilter> activityFilters = new HashSet<>();
+ activityFilters.add(
+ new ActivityFilter(ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT,
+ null));
+ final ActivityRule activityRule = new ActivityRule.Builder(activityFilters)
+ .setAlwaysExpand(true)
+ .build();
+
+ RuleController rc = RuleController.getInstance(this);
+
+ rc.addRule(activityRule);
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT));
+ }
+
/** R.id.launch_placeholder_split_button onClick */
public void launchPlaceholderSplit(View view) {
initializeSplitRules(createSplitPlaceholderRules());
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 9c3226b..0f5c003 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -99,6 +99,12 @@
FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
}
+ public static class AlwaysExpandActivity {
+ public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingAlwaysExpandActivity");
+ }
+
public static class PlaceholderPrimaryActivity {
public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 3d257b2..807f0c6 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -199,8 +199,7 @@
Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
TestActivity firstActivity = TestActivity.start(intent1);
// Show Ime with InputMethodManager to ensure the keyboard is on.
- boolean succ = callOnMainSync(firstActivity::showImeWithInputMethodManager);
- assertThat(succ).isTrue();
+ callOnMainSync(firstActivity::showImeWithInputMethodManager);
SystemClock.sleep(1000);
mInstrumentation.waitForIdleSync();
@@ -264,8 +263,7 @@
Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
ImeStressTestUtil.TestActivity secondActivity = activity.startSecondTestActivity(intent2);
// Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
- boolean succ = callOnMainSync(secondActivity::showImeWithInputMethodManager);
- assertThat(succ).isTrue();
+ callOnMainSync(secondActivity::showImeWithInputMethodManager);
SystemClock.sleep(1000);
mInstrumentation.waitForIdleSync();
// Close the second activity
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 299cbf1..0c267b2 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -26,8 +26,6 @@
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
-import static com.google.common.truth.Truth.assertThat;
-
import android.content.Intent;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
@@ -83,14 +81,11 @@
ImeStressTestUtil.TestActivity activity = ImeStressTestUtil.TestActivity.start(intent);
EditText editText = activity.getEditText();
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
-
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isTrue();
+ callOnMainSync(activity::showImeWithInputMethodManager);
verifyWindowAndViewFocus(editText, true, true);
waitOnMainUntilImeIsShown(editText);
- boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
- assertThat(hideResult).isTrue();
+ callOnMainSync(activity::hideImeWithInputMethodManager);
waitOnMainUntilImeIsHidden(editText);
}
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index b76a4eb..5c02124 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -102,12 +102,10 @@
for (int i = 0; i < iterNum; i++) {
String msgPrefix = "Iteration #" + i + " ";
Log.i(TAG, msgPrefix + "start");
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ callOnMainSync(activity::showImeWithInputMethodManager);
verifyShowBehavior(activity);
- boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
- assertThat(hideResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ callOnMainSync(activity::hideImeWithInputMethodManager);
verifyHideBehavior(activity);
}
@@ -128,14 +126,12 @@
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
String msgPrefix = "Iteration #" + i + " ";
Log.i(TAG, msgPrefix + "start");
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isTrue();
+ callOnMainSync(activity::showImeWithInputMethodManager);
waitOnMainUntil(
msgPrefix + "IME should be visible",
() -> !activity.isAnimating() && isImeShown(editText));
- boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
- assertThat(hideResult).isTrue();
+ callOnMainSync(activity::hideImeWithInputMethodManager);
waitOnMainUntil(
msgPrefix + "IME should be hidden",
() -> !activity.isAnimating() && !isImeShown(editText));
@@ -156,17 +152,13 @@
List<Integer> intervals = new ArrayList<>();
for (int i = 10; i < 100; i += 10) intervals.add(i);
for (int i = 100; i < 1000; i += 50) intervals.add(i);
- boolean firstHide = false;
for (int intervalMillis : intervals) {
String msgPrefix = "Interval = " + intervalMillis + " ";
Log.i(TAG, msgPrefix + " start");
- boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
- assertThat(hideResult).isEqualTo(firstHide);
- firstHide = true;
+ callOnMainSync(activity::hideImeWithInputMethodManager);
SystemClock.sleep(intervalMillis);
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isTrue();
+ callOnMainSync(activity::showImeWithInputMethodManager);
verifyShowBehavior(activity);
}
}
@@ -247,8 +239,7 @@
TestActivity activity = TestActivity.start(intent);
// Show InputMethodManager without requesting focus
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isFalse();
+ callOnMainSync(activity::showImeWithInputMethodManager);
int windowFlags = activity.getWindow().getAttributes().flags;
EditText editText = activity.getEditText();
@@ -474,8 +465,7 @@
Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
TestActivity activity = TestActivity.start(intent1);
// Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ callOnMainSync(activity::showImeWithInputMethodManager);
Thread.sleep(1000);
verifyShowBehavior(activity);
@@ -503,8 +493,7 @@
Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
TestActivity activity = TestActivity.start(intent);
// Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ callOnMainSync(activity::showImeWithInputMethodManager);
Thread.sleep(2000);
verifyShowBehavior(activity);
diff --git a/wifi/tests/src/android/net/wifi/nl80211/OWNERS b/wifi/tests/src/android/net/wifi/nl80211/OWNERS
new file mode 100644
index 0000000..8a75e25
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/nl80211/OWNERS
@@ -0,0 +1 @@
+kumachang@google.com